Background

What is Biliary Cholangitis?

Primary Biliary Cholangitis (previously called Primary Biliary Cirrhosis) is an autoimmune disease where bile ducts become swollen and inflamed and block the flow of bile. Bile is a substance that aids with digestion. The bile ducts carry bile from the liver to the small intestine. The swelling and inflammation can lead to scarring of the liver which is cirrhosis. Advanced cirrhosis can lead to liver failure or liver cancer. Medication can slow progression. There is no definite cure at this time.

Primary Biliary Cholangitis (Source)
Primary Biliary Cholangitis (Source)

Why is the data important to us?

Both of us have been very interetsted in the intersection of medicine and machine learning. The idea that the concepts we are learning in our DS majors could have such an impact on the health industry was really special. Another important aspect was that there is so much potential for advancement of medicine, we both agreed that this area is MLs most promising aspect. The cirrhosis data set perticularly stood out to us because the data set included so many interesting attributes and we anted to see how they are connected with the outcomes of the patient.

The data set also includes some important limitations. Most importantly, there are only 418 cases, greatly affecting the types of analysis we could do. This shows realm world problems data scientists face. Thus, our goal is to use our Statistical DS skillset to approach this problem, despite the limitations.

Objectives

Exploring the study, we concluded on two core questions we hope to address in this report:

CRQ1: What predictors, if any affected the status of the patient at the end of the study?

CRQ2: Are there factors not currently included in medical definition that can help distinguish different stages of Biliary Cholangitis?

Clinical Study + Dataset Background :

The dataset is produced from a clinical study by Mayo Clinic run from 1974-1984. The final ‘Status’ of each patient was observed in 1986. ‘Status’ was either Dead, Censored, or Censored due to liver transplant. A link to the original study is here: https://faculty.washington.edu/abansal/ShortCourse_DynamicDecisionMaking/Dickson1989_MayoPBCOriginalArticle.pdf

The data contains the following attributes (Data Source):

  1. ID: unique identifier
  2. N_Days: number of days between registration and the earlier of death, transplantation, or study analysis time in July 1986
  3. Status: status of the patient C (censored), CL (censored due to liver tx), or D (death)
  4. Drug: type of drug D-penicillamine or placebo
  5. Age: age in [days]
  6. Sex: M (male) or F (female)
  7. Ascites: presence of ascites N (No) or Y (Yes)
  8. Hepatomegaly: presence of hepatomegaly N (No) or Y (Yes)
  9. Spiders: presence of spiders N (No) or Y (Yes)
  10. Edema: presence of edema N (no edema and no diuretic therapy for edema), S (edema present without diuretics, or edema resolved by diuretics), or Y (edema despite diuretic therapy)
  11. Bilirubin: serum bilirubin in [mg/dl]
  12. Cholesterol: serum cholesterol in [mg/dl]
  13. Albumin: albumin in [gm/dl]
  14. Copper: urine copper in [ug/day]
  15. Alk_Phos: alkaline phosphatase in [U/liter]
  16. SGOT: SGOT in [U/ml]
  17. Triglycerides: triglicerides in [mg/dl]
  18. Platelets: platelets per cubic [ml/1000]
  19. Prothrombin: prothrombin time in seconds [s]
  20. Stage: histologic stage of disease (1, 2, 3, or 4)

For the basic pre-processing, we binary-encoded the categorical variables in the data set and set them as factors.


library(survival)
library(ggplot2)
library(survival)
library(survminer)
library(kableExtra)
library(factoextra)
#load in the data
library(tidyverse)
#load data
cirrhosis <- read_csv("cirrhosis.csv")
Rows: 418 Columns: 20── Column specification ────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): Status, Drug, Sex, Ascites, Hepatomegaly, Spiders, Edema
dbl (13): ID, N_Days, Age, Bilirubin, Cholesterol, Albumin, Copper, Alk_Phos, SGOT, Tryglicerides, Platelets, Pr...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
#encode the sex, ascites, hepatomegaly, spiders, edema into binary variables
cirrhosis$Sex <- ifelse(cirrhosis$Sex == "F", 0, 1)
cirrhosis$Ascites <- ifelse(cirrhosis$Ascites == "Y", 1, 0)
cirrhosis$Hepatomegaly <- ifelse(cirrhosis$Hepatomegaly == "Y", 1, 0)
cirrhosis$Spiders <- ifelse(cirrhosis$Spiders == "Y", 1, 0)
#factorize the stage
cirrhosis$Stage <- factor(cirrhosis$Stage)
cirrhosis$Status <- factor(cirrhosis$Status)
cirrhosis$Drug <- factor(cirrhosis$Drug)
cirrhosis$Sex <- factor(cirrhosis$Sex)
#cirrhosis$Ascites <- factor(cirrhosis$Ascites)
#cirrhosis$Hepatomegaly <- factor(cirrhosis$Hepatomegaly)
#cirrhosis$Spiders <- factor(cirrhosis$Spiders)
cirrhosis$Edema <- factor(cirrhosis$Edema)
cirrhosis$Sex <- factor(ifelse(cirrhosis$Sex == 0, "Female", "Male"))

#Drop the first column
cirrhosis <- cirrhosis[,-1]
#?????
cirrhosis %>% 
  ggplot(aes(x = N_Days)) + geom_histogram() +
  facet_wrap(facets = vars(Status))

#Required libraries
library(survival)
library(ggplot2)
library(survival)
library(survminer)

Exploratory Data Analysis

Part 1: Exploring the demographics of the patients

Sex Ratio of Patients


#cirrhosis$Sex <- factor(ifelse(cirrhosis$Sex == 0, "Female", "Male"))

# Preparing the data for pie chart
sex_data <- cirrhosis %>% 
            count(Sex) %>% 
            mutate(Percentage = n / sum(n) * 100)

# Pie Chart
ggplot(sex_data, aes(x="", y=Percentage, fill=Sex)) +
    geom_bar(stat="identity", width=1) +
    coord_polar("y", start=0) +
    scale_fill_brewer(palette="Pastel1") +
    theme_minimal() +
    theme(axis.line = element_blank(),
          axis.text = element_blank(),
          axis.ticks = element_blank(),
          panel.grid = element_blank(),
          plot.title = element_text(size=14, face="bold")) +
    labs(fill="Sex", title=" Sex Amongst Patients")

NA
NA
NA
NA

Primary Biliary Cholangitis affects women much more than men at a 10-1 ratio. The data of this clinical study mimics the greater population. Investigating gender-based differences in disease progression can uncover any gender-specific patterns in PBC. This could lead to gender-tailored treatment approaches and a better understanding of the disease’s biology, which might differ between males and females.

Age Distribution of Patients

# Converting age from days to years for better readability
cirrhosis$Age_Years <- cirrhosis$Age / 365.25

# Histogram
ggplot(cirrhosis, aes(x=Age_Years)) +
    geom_histogram(binwidth=5, fill="#69b3a2", color="#e9ecef") +
    theme_minimal() +
    labs(title="Age Distribution of PBC Patients", x="Age (years)", y="Count") +
    theme(plot.title = element_text(size=14, face="bold"))

The distribution of ages is quite normal.

Part 2: Looking at the data itself

NA counts amongst our variables

count <-  data.frame(NA_CountsPerVar = colSums(is.na(cirrhosis)))
kable(count)
NA_CountsPerVar
N_Days 0
Status 0
Drug 106
Age 0
Sex 0
Ascites 106
Hepatomegaly 106
Spiders 106
Edema 0
Bilirubin 0
Cholesterol 134
Albumin 0
Copper 108
Alk_Phos 106
SGOT 106
Tryglicerides 136
Platelets 11
Prothrombin 2
Stage 6
Age_Years 0

An observation is that many of the variables have 106 NAs. This indicates that a good fraction [106 patients] could have been equally tracked and measured in a less extensive way.

Distribution of Stages of Biliary Cholangitis


cirrhosis %>%
      ggplot(aes(x = Stage)) + geom_bar(fill = "#097969") # need color

This distribution is left skewed and not symmetric. Most patients have stage 3 and then 4 of Biliary Cholangitis.

Part 3: Exploring some interactions of variables

How does prescense of a D-penicillamine with stage of Biliary Cholangitis impact number of days till death?


ggplot(data = cirrhosis, aes (Drug, Stage, fill = N_Days))+ geom_tile() + scale_fill_distiller(palette = "RdPu", trans = "reverse") 

This heat map shows that being a placebo in stage 1 gives you a greater amount of days till death in this sample of patients.

Individuals with Edema, and if they were on the drug


hi = c("#40B5AD", "#009E60", "#9FE2BF")
library(ggmosaic)
cirrhosis %>%
  ggplot() +
  geom_mosaic(aes( x = product(Edema), fill = Drug)) + scale_fill_manual(values = hi)

Edema is swelling due to too much liquid trapped in the body’s tissues. It’s common complication in liver diseases and can significantly impact patient quality of life and survival. Understanding how different levels of edema (none, controlled by diuretics, or persistent despite treatment) affect survival can inform patient management strategies and highlight the need for aggressive interventions in certain cases. From the graph we can see a even split between the placebo and drug for Edema status. There is no NA values for Edema persistent despite diuretics. We cannot directly compare it the splits for other statuses given their NA values if known could change the look of the graph.

Survival Analysis Based on Treatment and Stage: How does the survival rate differ among patients at different stages of PBC who received D-penicillamine versus placebo?

This question investigates the effectiveness of the drug D-penicillamine compared to a placebo, considering the stage of PBC. By analyzing survival rates across different disease stages and treatment types, we can assess the drug’s effectiveness at various disease stages. This information is vital for clinicians to make informed decisions about treatment plans and for researchers to understand the drug’s impact.

surv_object <- Surv(cirrhosis$N_Days, cirrhosis$Status == 'D')
surv_fit <- survfit(surv_object ~ cirrhosis$Drug + cirrhosis$Stage, data = cirrhosis)
ggsurvplot(surv_fit, 
           data = cirrhosis,
           conf.int = TRUE, 
           #palette = c("#00AFBB", "#E7B800", "#FC4E07"),
           xlab = "Days", 
           ylab = "Survival Probability", 
           title = "Survival Curves by Treatment and Stage")

Impact of Liver Complications on Survival: How do the presence of ascites, hepatomegaly, and spiders relate to survival time?

Exploring how liver-related symptoms (ascites, hepatomegaly, and spiders) affect patient survival provides insights into the severity of these complications in PBC progression. This can help in identifying high-risk patients and understanding the disease’s impact on liver function.

surv_fit_complications <- survfit(surv_object ~ cirrhosis$Ascites + cirrhosis$Hepatomegaly + cirrhosis$Spiders, data = cirrhosis)
ggsurvplot(surv_fit_complications, 
           data = cirrhosis,
           conf.int = TRUE, 
           #palette = c("#2E9FDF", "#FC4E07", "#6ACC65"),
           xlab = "Days", 
           ylab = "Survival Probability", 
           title = "Survival Curves by Liver Complications")

Correlation between Biochemical Markers and Disease Stage: What is the correlation between biochemical markers (Bilirubin, Cholesterol, Albumin, Copper, Alk_Phos, SGOT, Triglycerides) and the histologic stage of the disease?

This analysis is crucial to understand how different biochemical markers correlate with the disease’s progression. Such correlations can aid in the early detection of disease severity, help in monitoring the disease progression, and potentially guide treatment adjustments.

Part I: Classification Algorithm for the Status of the Patient using CART

To address the first sub-goal of this project, we will be exploring the prediction of the status of a patient at the end of the study. Our main objective through this is to gain a stronger understanding of what factors played in the role of the death of the patient. To do this, we will be using Decision Trees. Let us start by exploring what these are and why we chose to use them.

Introduction

Decision trees are a type of model used in statistics for making predictions based on data. They work by breaking down a dataset into smaller subsets through “splits,” resembling a tree with branches. Each branch represents a possible decision or outcome, leading to a final prediction or classification.

The main advantages of decision trees include their simplicity and interpretability, as they are easy to visualize and understand. Another important advantage is that they are able to learn with data with N/A values, something alternatives such as Logistic Regression. Considering the limitation we have with our data size and number of N/A values, this an important advantage. However, there are some downsides such as a tendency to overfit the data, which is something we need to be careful about.

Decision Tree Example (Source)
Decision Tree Example (Source)

Pre-processing the data

For this data, we will be dealing with the Status variable. The Status variable was divided into 3 classes:

  • Class 0 (D): The patient didn’t survive by the end of the observation
  • Class 1 (C): The patient is censored, meaning that the observation period ended without the death being recorded
  • Class 2 (CL): Similar to Class 1, the patient is censored due to liver transplantation

Thus, we can group Class 1 and 2.

#Combine C and CL status into one variable and binarize
cirrhosisTreeData <- cirrhosis
cirrhosisTreeData$Status <- ifelse(cirrhosisTreeData$Status == "C" | cirrhosisTreeData$Status == "CL", 1, 0)

Methodology

Our objective is to create a classifier capable of predicting a patient’s outcome. To achieve this, we will be testing our data with the cart algorithm. To ensure model validation, we’ll be using a 80% training and 20% testing data division. I will also be stratifying the data based on the status.

To guarantee consistency and reproducibility in our results, we have fixed the seed for our 80/20 data split at 380. With these steps, we are now well-positioned to finalize our training and testing data sets.

The predictors in these models will be guided by the results from the EDA. We will also use a variety of tools to understand the model’s performance.

# Wrangle the Graduate data to set up training and testing datasets
modelCirrhosis <- cirrhosisTreeData %>%
  #drop_na() %>%
  mutate(
    tempID = row_number(),
    .before = Status
  )

## Set seed for reproducibility and slice ----
set.seed(380)
trainingData <- modelCirrhosis %>%
  group_by(Status) %>%
  slice_sample(prop = 0.8)

testingData <- modelCirrhosis %>%
  filter(!(tempID %in% trainingData$tempID))

Step 1: Growing the tree

There are useful packages to build decision trees in R: the {tree} and the {rpart} (recursive partitioning) packages.

In this report, we have decided to use {rpart} because it provides more flexibility for surrogate splits and the trees are a bit easier to make attractive looking.

# Grow Graduate tree via rpart package
library(rpart)
rPartStatus <- rpart(
  formula = Status ~ Drug + Age + Sex + Ascites + Hepatomegaly + Spiders + Edema + Bilirubin + Cholesterol + Albumin + Copper + Alk_Phos + SGOT + Platelets + Prothrombin + Stage + Tryglicerides, 
  data = trainingData,
  method = "class",
  parms = list(split = "information")
  # We did not need to use the control parameters
)

Part 2: Visualizing the tree

With the tree grown, we can now visualize it for an easy understanding of its functioning. This is an important advantage for CART over logistic regression.

The following is a basic diagram for the tree that was just grown.

# Display rpart.plot ----
 library(rpart.plot)
rpart.plot(
  x = rPartStatus,
  type = 2,
  extra = 101
)

To gain a further understanding of the data, we can plot a tree yielding Collection Node style trees. This can help us understand how the data is split.

# Using the rattle package to visualize the tree ----
library(rattle)

fancyRpartPlot(
  model = rPartStatus,
  main = NULL,
  sub = NULL
)

The tree shows us the splits that were done on Age, Bilirubin and Prothrombin. Interestingly, Stage did not contribute in the tree.

Part 3: Pruning the tree

Pruning reduces the size of decision trees by removing parts of the tree that do not provide power to classify instances. The first step of pruning a tree is understanding the complexity parameter used. The complexity parameter (cp) in rpart is the minimum improvement in the model needed at each node. This is used when building the tree. We can see the results based on the cross validation from the table below.

invisible(capture.output({cpTable <- printcp(rPartStatus)}))

library(kableExtra)

kable(
  x = cpTable,
  col.names = c("CP", "Num. of splits", "Rel. Error",
                "Mean Error", "Std. Deviation of Error"),
  digits = 3,
  booktabs = TRUE,
  align = "c",
  table.attr = 'data-quarto-disable-processing="true"'
)
CP Num. of splits Rel. Error Mean Error Std. Deviation of Error
0.375 0 1.000 1.000 0.069
0.109 1 0.625 0.711 0.064
0.035 2 0.516 0.695 0.063
0.023 4 0.445 0.633 0.061
0.020 5 0.422 0.641 0.061
0.010 7 0.383 0.648 0.062

This can also be visualized in a graph to gain a better understanding of the data. The graph below shows the connection between the cp, size of tree and the x-val relative error.

plotcp(
  x = rPartStatus,
  minline = TRUE,
  upper = "size"
)

From the graph we can see that a cp of 0.039 is ideal as it is under the horizontal (dotted) reference line. We can prune the tree with this CP value.

# Prune the rpart Graduate Tree ----
rPartStatus2 <- prune(
  tree = rPartStatus,
  cp = 0.029
)

We can plot the pruned tree

fancyRpartPlot(
  model = rPartStatus2,
  main = NULL,
  sub = NULL
)

We ca see the pruned tree has cut out some leaf nodes. This would help in avoiding overfitting the model.

Part 4: Results

Now, we can evaluate the results of the tree on the testing data from the initial 80-20 split. As is true whenever we use validation approaches, we need to test out our model on the testing data set. This will give us a more accurate understanding of how well the model fits the context we’re seeking to build our understanding of.

An important part of our results is understanding the role of prediction and inference. In a broad sense, prediction refers to the process of making forecasts about future events or unknown values based on a model while inference generally refers to the process of drawing conclusions from data. For the basic tree, I will be mainly focusing on prediction aspects of the results. However, later in the report, we will also be exploring inference findings.

pred_StatusRpart2 <- predict(
  object = rPartStatus2,
  newdata = testingData,
  type = "prob"
)

# Data Wrangling the predictions ----
StatusPrediction <- data.frame(
  rpart2_non_death = pred_StatusRpart2[, 1],
  rpart2_death = pred_StatusRpart2[, 2]
) %>%
  mutate(
    rpart2_pred = ifelse(
      test = rpart2_death > rpart2_non_death,
      yes = 1,
      no = 0
    )
  )

## Set predictions as factors
StatusPrediction$rpart2_pred <- as.factor(StatusPrediction$rpart2_pred)

# Merge supervision column into predictions data frame ----
StatusPrediction <- cbind(
  tempID = testingData$tempID,
  Status = testingData$Status,
  StatusPrediction
)

We can evaluate the results of this through a confusion matrix.

StatusPrediction$Status <- factor(StatusPrediction$Status)

library(yardstick)

# Build confusion matrix for second tree model
conf_matrix <- conf_mat(
  data = StatusPrediction,
  truth = Status,
  estimate = rpart2_pred
)$table

kable(
  conf_matrix,
  col.names = c("Prediction/Supervision", "0", "1"),
  digits = 3,
  booktabs = TRUE,
  caption = "Model 1: Confusion Matrix (0=Deceased, 1=Censored)",
  align = "c"
) %>%
kable_styling(latex_options = "HOLD_position")
Model 1: Confusion Matrix (0=Deceased, 1=Censored)
Prediction/Supervision 0 1
0 19 9
1 14 43


accuracy <- accuracy(StatusPrediction, Status, rpart2_pred)
specificity <- specificity(StatusPrediction, Status, rpart2_pred)
sensitivity <- sensitivity(StatusPrediction, Status, rpart2_pred)
# Build a data frame with model metrics ----
StatusPreds <- StatusPrediction %>%
  dplyr::select(tempID, Status, contains("_pred")) %>%
  pivot_longer(
    cols = !c(tempID, Status),
    names_to = "model",
    values_to = "prediction"
  )

accuracy <- StatusPreds %>%
  group_by(model) %>%
  accuracy(
    truth = Status,
    estimate = prediction
  )

sensitivity <- StatusPreds %>%
  group_by(model) %>%
  sensitivity(
    truth = Status,
    estimate = prediction,
    event_level = "second"
  )

specificity <- StatusPreds %>%
  group_by(model) %>%
  specificity(
    truth = Status,
    estimate = prediction,
    event_level = "second"
  )

modelMetrics <- bind_rows(
  accuracy,
  sensitivity,
  specificity
)

With this, we can also calculate the model’s metrics on the test data.

# Make a nice looking table of model metrics ----
modelMetrics %>%
  dplyr::select(model, .metric, .estimate) %>%
  pivot_wider(
    id_cols = model,
    names_from = .metric,
    values_from = .estimate
  ) %>%
  kable(
    digits = 3,
    booktabs = TRUE,
    align = "c",
    table.attr = 'data-quarto-disable-processing="true"'
  )
model accuracy sensitivity specificity
rpart2_pred 0.729 0.827 0.576

As you can see the model shows good accuracy with 72.9%. However, the specificity is an issue with 57.6%. Now let us compare this with a different type of model: logistic regression.

Part II: Exploring the predictors for the patient’s status using logistic regression

Next, let us explore our question with a different type of model and compare the results with our tree. To do this, we will be using (binary) logistic regression model.

Introduction

Logistic regression is a statistical method used for binary classification, which predicts the probability of an outcome that can be either true or false. This is done by understanding the relationship between a dependent binary variable and one or more independent variables. Logistic regression is easy to implement and interpret. However there are also some drawbacks, the model assumes a linear relationship between the independent variables and the log odds of the dependent variable, which may not always hold true in complex real-world scenarios. Furthermore, unlike the decision tree, linear regression models cannot ignore N/A values.

Logistic Regression Model (Source)
Logistic Regression Model (Source)

Methodology

Similar to the tree, we will start by splitting the data set into training and testing sets. Note that the data now only contains instances where the patient was deceased. The training set will be used to train our model, while the testing set will help evaluate its performance. We’ll use 80% of the data for training and the remaining 20% for testing. To allow reproducible code, we have fixed the seed at 380.

With this, we will build two candidate models:

  • The first model will test the classification based on just the SGOT

  • The second model will use a step wise function using various predictors to see the best performance.

Another important consideration is the application of prediction (estimating an outcome based on input variables) and inference (understanding the relationships between variables). We will evaluate the inference through the coefficient analysis and prediction through roc curves and confusion matrix. It is important to note that these metrics will complement each other in our understanding of the data. However, the main focus of this analysis will be prediction and we will work with several metrics to evaluate it.

We will use a variety of tools to understand the model’s performance.

cirrhosisRegression <- cirrhosis
cirrhosisRegression$Status <- ifelse(cirrhosisRegression$Status == "C" | cirrhosisRegression$Status == "CL", 1, 0)
#model data
LRmodelData <- cirrhosisRegression %>%
  drop_na() %>%
  mutate(
    tempID = row_number(),
    .before = Status
  )

# Set seed for reproducibility and slice
set.seed(380)
trainingData <- LRmodelData %>%
  group_by(Status) %>%  # group_by() function ensures that the data
  slice_sample(prop = 0.80)

testingData <- LRmodelData %>%
  filter(!(tempID %in% trainingData$tempID))

trainingResults <- trainingData
# Form Candidate Model 1
model1 <- glm(
  formula = Status ~ SGOT,
  data = trainingData,
  family = binomial
)

Stepwise results:

# Lower bound (Intercept only)
lower <- glm(
  formula = Status ~ 1,
  data = trainingData,
  family = binomial
)

# Upper bound 
upper <- glm(
  formula = Status ~ Drug + Age + Sex + Ascites + Hepatomegaly + Spiders + Edema + Bilirubin + Cholesterol + Albumin + Copper + Alk_Phos + SGOT + Platelets + Prothrombin + Stage,
  data = trainingData,
  family = binomial
)


# Stepwise search
model2 <- step(
  object = lower,
  scope = list(
    lower = lower,
    upper = upper
  ),
  data = trainingData,
  direction = "both",
  k = 2
)
Start:  AIC=298.13
Status ~ 1

               Df Deviance    AIC
+ Bilirubin     1   247.26 251.26
+ Prothrombin   1   257.25 261.25
+ Copper        1   261.84 265.84
+ Edema         2   265.53 271.53
+ Ascites       1   273.57 277.57
+ Stage         3   270.16 278.16
+ Albumin       1   274.21 278.21
+ Alk_Phos      1   274.91 278.91
+ Hepatomegaly  1   275.05 279.05
+ Age           1   275.43 279.43
+ Spiders       1   278.02 282.02
+ SGOT          1   286.47 290.47
+ Platelets     1   291.25 295.25
+ Cholesterol   1   291.70 295.70
+ Sex           1   292.37 296.37
+ Drug          1   293.91 297.91
<none>              296.12 298.12

Step:  AIC=251.26
Status ~ Bilirubin

               Df Deviance    AIC
+ Age           1   225.30 231.30
+ Prothrombin   1   226.27 232.27
+ Alk_Phos      1   233.06 239.06
+ Edema         2   231.75 239.75
+ Albumin       1   238.01 244.01
+ Ascites       1   238.45 244.45
+ Stage         3   235.74 245.74
+ Copper        1   240.06 246.06
+ Spiders       1   241.63 247.63
+ Hepatomegaly  1   242.06 248.06
+ Platelets     1   243.87 249.87
+ Sex           1   244.21 250.21
+ Drug          1   244.37 250.37
+ Cholesterol   1   245.13 251.13
<none>              247.26 251.26
+ SGOT          1   247.14 253.14
- Bilirubin     1   296.12 298.12

Step:  AIC=231.3
Status ~ Bilirubin + Age

               Df Deviance    AIC
+ Alk_Phos      1   209.76 217.76
+ Prothrombin   1   212.33 220.33
+ Edema         2   213.68 223.68
+ Copper        1   218.03 226.03
+ Spiders       1   218.93 226.93
+ Albumin       1   219.94 227.94
+ Ascites       1   220.76 228.76
+ Stage         3   217.58 229.58
+ Hepatomegaly  1   221.76 229.76
+ SGOT          1   222.78 230.78
<none>              225.30 231.30
+ Platelets     1   223.57 231.57
+ Drug          1   223.83 231.83
+ Cholesterol   1   224.86 232.86
+ Sex           1   225.06 233.06
- Age           1   247.26 251.26
- Bilirubin     1   275.43 279.43

Step:  AIC=217.76
Status ~ Bilirubin + Age + Alk_Phos

               Df Deviance    AIC
+ Prothrombin   1   197.16 207.16
+ Edema         2   198.44 210.44
+ Spiders       1   201.18 211.18
+ Stage         3   201.16 215.16
+ Ascites       1   205.18 215.18
+ Platelets     1   205.24 215.24
+ Copper        1   205.94 215.94
+ Hepatomegaly  1   206.62 216.62
+ Albumin       1   206.69 216.69
<none>              209.76 217.76
+ SGOT          1   208.15 218.15
+ Cholesterol   1   208.82 218.82
+ Drug          1   208.84 218.84
+ Sex           1   209.68 219.68
- Alk_Phos      1   225.30 231.30
- Age           1   233.06 239.06
- Bilirubin     1   251.55 257.55

Step:  AIC=207.16
Status ~ Bilirubin + Age + Alk_Phos + Prothrombin

               Df Deviance    AIC
+ Spiders       1   191.39 203.39
+ Edema         2   189.41 203.41
+ Stage         3   188.90 204.90
+ Copper        1   193.94 205.94
+ Albumin       1   194.23 206.23
+ Hepatomegaly  1   194.37 206.37
+ SGOT          1   194.45 206.45
+ Drug          1   194.58 206.58
+ Platelets     1   194.95 206.95
+ Ascites       1   195.07 207.07
<none>              197.16 207.16
+ Cholesterol   1   197.12 209.12
+ Sex           1   197.16 209.16
- Prothrombin   1   209.76 217.76
- Age           1   211.78 219.78
- Alk_Phos      1   212.33 220.33
- Bilirubin     1   225.31 233.31

Step:  AIC=203.39
Status ~ Bilirubin + Age + Alk_Phos + Prothrombin + Spiders

               Df Deviance    AIC
+ Edema         2   185.35 201.35
+ SGOT          1   188.49 202.49
+ Drug          1   188.82 202.82
+ Copper        1   189.22 203.22
+ Albumin       1   189.33 203.33
+ Ascites       1   189.38 203.38
<none>              191.39 203.39
+ Hepatomegaly  1   189.64 203.64
+ Stage         3   185.74 203.74
+ Platelets     1   189.96 203.96
+ Sex           1   191.13 205.13
+ Cholesterol   1   191.37 205.37
- Spiders       1   197.16 207.16
- Prothrombin   1   201.18 211.18
- Age           1   207.34 217.34
- Alk_Phos      1   208.03 218.03
- Bilirubin     1   214.93 224.93

Step:  AIC=201.35
Status ~ Bilirubin + Age + Alk_Phos + Prothrombin + Spiders + 
    Edema

               Df Deviance    AIC
+ SGOT          1   181.77 199.77
+ Drug          1   182.57 200.57
<none>              185.35 201.35
+ Copper        1   183.38 201.38
+ Albumin       1   183.48 201.48
+ Hepatomegaly  1   184.08 202.08
+ Stage         3   180.39 202.39
+ Platelets     1   184.40 202.40
+ Sex           1   185.10 203.10
+ Ascites       1   185.32 203.32
+ Cholesterol   1   185.35 203.35
- Edema         2   191.39 203.39
- Spiders       1   189.41 203.41
- Prothrombin   1   192.56 206.56
- Age           1   199.74 213.74
- Alk_Phos      1   201.49 215.49
- Bilirubin     1   206.71 220.71

Step:  AIC=199.77
Status ~ Bilirubin + Age + Alk_Phos + Prothrombin + Spiders + 
    Edema + SGOT

               Df Deviance    AIC
+ Drug          1   178.92 198.92
<none>              181.77 199.77
+ Copper        1   179.90 199.90
+ Albumin       1   180.44 200.44
+ Hepatomegaly  1   180.66 200.66
+ Stage         3   177.01 201.01
+ Platelets     1   181.13 201.13
- SGOT          1   185.35 201.35
+ Sex           1   181.53 201.53
+ Cholesterol   1   181.68 201.68
+ Ascites       1   181.70 201.70
- Spiders       1   185.80 201.80
- Edema         2   188.49 202.49
- Prothrombin   1   189.92 205.92
- Bilirubin     1   196.19 212.19
- Alk_Phos      1   196.52 212.52
- Age           1   198.70 214.70

Step:  AIC=198.92
Status ~ Bilirubin + Age + Alk_Phos + Prothrombin + Spiders + 
    Edema + SGOT + Drug

               Df Deviance    AIC
+ Copper        1   176.90 198.90
<none>              178.92 198.92
+ Stage         3   173.10 199.10
+ Hepatomegaly  1   177.38 199.38
+ Albumin       1   177.49 199.49
- Drug          1   181.77 199.77
+ Platelets     1   178.42 200.42
- SGOT          1   182.57 200.57
+ Sex           1   178.69 200.69
+ Cholesterol   1   178.73 200.73
- Spiders       1   182.83 200.83
+ Ascites       1   178.89 200.89
- Edema         2   185.85 201.85
- Prothrombin   1   188.64 206.64
- Alk_Phos      1   193.00 211.00
- Bilirubin     1   193.77 211.77
- Age           1   194.21 212.21

Step:  AIC=198.9
Status ~ Bilirubin + Age + Alk_Phos + Prothrombin + Spiders + 
    Edema + SGOT + Drug + Copper

               Df Deviance    AIC
<none>              176.90 198.90
- Copper        1   178.92 198.92
+ Hepatomegaly  1   175.19 199.19
+ Albumin       1   175.31 199.31
+ Stage         3   171.76 199.76
- Drug          1   179.90 199.90
- Spiders       1   180.12 200.12
+ Platelets     1   176.38 200.38
- SGOT          1   180.52 200.52
+ Ascites       1   176.82 200.82
+ Cholesterol   1   176.87 200.87
+ Sex           1   176.90 200.90
- Edema         2   183.63 201.63
- Bilirubin     1   184.57 204.57
- Prothrombin   1   186.72 206.72
- Alk_Phos      1   188.66 208.66
- Age           1   191.54 211.54

Results

Initially, we’ll delve into the two preliminary models independently to understand where they stand. Following that, we’ll be deploying the best candidate model on our test data. Regarding confusion matrices, we’ll employ a basic rule: if the predicted probability of a the patient’s status being “deceased” exceeds 0.5, we’ll categorize them as deceased (naïve rule).

Model 1

# Model 1 Coefficient Table
as.data.frame(summary(model1)$coefficients) %>%
  rownames_to_column(var = "X") %>%
  rename(coefficient = Estimate) %>% 
  mutate(
    prob_odds = case_when(
      coefficient == "(Intercept)" ~ exp(coefficient)/(1 + exp(coefficient)),
      .default = exp(coefficient)
    ),
    .after = coefficient
  ) %>%
  mutate(
    `Pr(>|z|)` = ifelse(
      test = `Pr(>|z|)` < 0.001,
      yes = paste("< 0.001"),
      no = `Pr(>|z|)`
    ),
    X = case_when(
      X == "(Intercept)" ~ "Intercept",
      grepl(x = X, pattern = "SGOT") ~ "SGOT"
    )
  ) %>%
  kable()
X coefficient prob_odds Std. Error z value Pr(>|z|)
Intercept 1.3728804 3.9467022 0.3566274 3.849622 < 0.001
SGOT -0.0077204 0.9923094 0.0026073 -2.961056 0.00306586722502071
NA

This table shows us the results of our first model. We can see that, holding other variables constant, a one-unit increase in SGOT is associated with a decrease in the log-odds of the response variable by 0.0076446. Furthermore, the odds-ratio indicates that for each one-unit increase in SGOT, the odds of the event occurring decrease by about 0.76%.

We can also plot the confusion matrix for this model:

library(janitor)
# Building confidence intervals for Model 1 coefficients
model1CI <- confint(
  object = model1,
  parm = "SGOT",
  level = 0.9
)
Waiting for profiling to be done...
trainingResults <- trainingData %>%
  ungroup() %>%
  mutate(model1Pred = predict(object = model1, newdata = ., type = "response"))

# Apply naïve rule ----
trainingResults <- trainingResults %>%
  mutate(
    model1Class = case_when(
      model1Pred > 0.5 ~ "Censored",
      .default = "Deceased"
    )
  )

#Confusion Matrix for Model 1
trainingResults %>%
  mutate(Patient_status = ifelse(Status == 1, "Censored", "Deceased")) %>%
  tabyl(var1 = model1Class, var2 = Patient_status) %>%
  adorn_title(
    placement = "combined",
    row_name = "Predicted",
    col_name = "Actual"
  ) %>%
  kable(
    booktabs = TRUE,
    align = "c",
    caption = "Model 1 Confusion Matrix"
  )%>%kable_styling(latex_options = "HOLD_position")
Model 1 Confusion Matrix
Predicted/Actual Censored Deceased
Censored 117 68
Deceased 15 20
NA

We can see that this model tends to over predict censored values. This shows the need of bringing in more factors. Next let us look at our Model 2, which has multiple factors as discussed earlier.

#Coeff for model 2
as.data.frame(summary(model2)$coefficients) %>%
  rename(coefficient = Estimate) %>% 
  mutate(
    prob_odds = case_when(
      coefficient == "(Intercept)" ~ exp(coefficient)/(1 + exp(coefficient)),
      TRUE ~ exp(coefficient)
    ),
    .after = coefficient
  ) %>%
  kable()
coefficient prob_odds Std. Error z value Pr(>|z|)
(Intercept) 13.1738171 5.264002e+05 2.5758130 5.1144306 0.0000003
Bilirubin -0.2146715 8.068064e-01 0.0865790 -2.4794883 0.0131571
Age -0.0002039 9.997962e-01 0.0000571 -3.5727423 0.0003533
Alk_Phos -0.0002988 9.997013e-01 0.0000968 -3.0870015 0.0020219
Prothrombin -0.6086022 5.441109e-01 0.2125486 -2.8633552 0.0041918
Spiders -0.7375877 4.782662e-01 0.4110588 -1.7943606 0.0727556
EdemaS -0.0940912 9.101997e-01 0.6115311 -0.1538617 0.8777188
EdemaY -16.3353205 1.000000e-07 857.1377366 -0.0190580 0.9847948
SGOT -0.0064630 9.935579e-01 0.0033599 -1.9235635 0.0544093
DrugPlacebo 0.6736154 1.961316e+00 0.3939801 1.7097702 0.0873084
Copper -0.0038998 9.961078e-01 0.0027552 -1.4154168 0.1569463
NA

This is the role of inference in evaluating our model. The most notable predictors are Bilirubin, Age, Alk_Phos, and Prothrombin, each showing a statistically significant relationship (p < 0.05) with the dependent variable. The Intercept and EdemaY have extremely significant p-values, but the practical significance of EdemaY is questionable due to its large standard error. Other variables like Spiders, SGOT, DrugPlacebo, and Copper, while contributing to the model, do not reach conventional levels of statistical significance (p < 0.05).

#do the Tukey-Anscombe plot
ggplot(
  data = data.frame(
    residuals = residuals(model2, type = "pearson"),
    fitted = fitted(model2)
  ),
  mapping = aes(x = fitted, y = residuals)
) +
  geom_point() +
  geom_smooth(
    formula = y ~ x,
    method = stats::loess,
    method.args = list(degree = 1),
    se = FALSE,
    linewidth = 0.5
  ) +
  theme_bw() +
  labs(
    x = "Fitted",
    y = "Pearson Residuals"
  )

This figure shows us the Tukey-Anscombe plot using Pearson residuals for Model 2. In an ideal fit, the residuals should be evenly distributed about zero with constant mean and variance. The shape of the line suggests that the model is not capturing someunder lying structure in the datain extreme cases.

#plot the gvif
as.data.frame(car::vif(model2)) %>%
  kable(
    digits = 3,
    align = "lcccc",
    booktab = TRUE,
    format.args = list(big.mark = ","),
    table.attr = 'data-quarto-disable-processing="true"',
    label = "GVIF analsyis"
  )
GVIF Df GVIF^(1/(2*Df))
Bilirubin 1.419 1 1.191
Age 1.232 1 1.110
Alk_Phos 1.033 1 1.017
Prothrombin 1.117 1 1.057
Spiders 1.038 1 1.019
Edema 1.119 2 1.029
SGOT 1.252 1 1.119
Drug 1.091 1 1.044
Copper 1.301 1 1.140
NA

The Variance Inflation Factor (VIF) values for the variables in the model (Bilirubin, Age, Alk_Phos, Prothrombin, Spiders) are all close to 1, indicating minimal multicollinearity. This means that these predictors are relatively independent of each other, enhancing the reliability of the model.

#Store the predicted and actual values for Model 2
trainingResults$model2Pred <- predict(model2, type = "response")
trainingResults$model2Class <- ifelse(trainingResults$model2Pred > 0.5, "Censored", "Deceased")
trainingResults$Actual <- ifelse(trainingData$Status == 1, "Censored", "Deceased")

# Create confusion matrix using table
confusionMatrixRegression <- table(Predicted = trainingResults$model2Class, Actual = trainingResults$Actual)

kable(confusionMatrixRegression, caption = "Confusion matrix for Model 2") %>%
  kable_classic(latex_options = "HOLD_position")
Confusion matrix for Model 2
Censored Deceased
Censored 115 24
Deceased 17 64
NA

From this confusion matrix we can see the relationships between the True Positive, True Negative, False Positive and False Negative values. From this we can calculate:

  • Accuracy: Approximately 81.36%

  • Recall: Approximately 79.01%

  • Precision: Approximately 72.73%

  • F1 Score: Approximately 75.74%

Lastly, let us look at the separation plots for each of the models.

library(pROC)
library(separationplot)
# Fit ROC Curves for later
## Model 1
model1ROC <- roc(
  formula = Status ~ model1Pred,
  data = trainingResults
)
Setting levels: control = 0, case = 1
Setting direction: controls < cases
model1ROC_df <- data.frame(
  threshold = model1ROC$thresholds,
  sensitivity = model1ROC$sensitivities,
  specificity = model1ROC$specificities,
  model = "Model 1"
)
## Model 2
model2ROC <- roc(
  formula = Status ~ model2Pred,
  data = trainingResults
)
Setting levels: control = 0, case = 1
Setting direction: controls < cases
model2ROC_df <- data.frame(
  threshold = model2ROC$thresholds,
  sensitivity = model2ROC$sensitivities,
  specificity = model2ROC$specificities,
  model = "Model 2"
)
# Convert 'Actual' column to numeric 0/1
trainingResults <- trainingResults %>%
  mutate(
    actualNum = if_else(Actual == "Deceased", 0, 1)
  )


#Sepeation Plot
par(mar = c(4,0,0,0))
separationplot(
  pred = trainingResults$model1Pred, 
  actual = trainingResults$actualNum, 
  type = "rect",
  line = TRUE, 
  lwd2 = 2,
  show.expected = TRUE, 
  newplot = FALSE,
  heading = "Model 1"
)

#Sepeation Plot
par(mar = c(4,0,0,0))
separationplot(
  pred = trainingResults$model2Pred, 
  actual = trainingResults$actualNum, 
  type = "rect",
  line = TRUE, 
  lwd2 = 2,
  show.expected = TRUE, 
  newplot = FALSE,
  heading = "Model 2"
)

The separation plot assesses the the fit of the model by providing the model’s ability to predict occurrences with a high probability and non-occurrences with low probability. The separation plot above separation plot suggests that Model 2 has a reasonably good performance in predicting the patient’s status, especially for the observations on the left-most side of the plot compared to Model 1 with the training data. We will later compare this graph with the testing data.

Lastly, let us look at the ROC curves for both the models.

## Merge into existing data frame
rocData <- rbind(model1ROC_df, model2ROC_df)

## AUC Data
aucData <- data.frame(
  model = c("Model 1", "Model 2"),
  auc = c(model1ROC$auc, model2ROC$auc)
)
#ROC plot
ggplot(
  data = rocData,
  mapping = aes(x = 1 - specificity, y = sensitivity, color = model)
) +
  geom_path() +
  geom_abline(
    slope = 1,
    intercept = 0,
    linetype = "dotted"
  ) +
  geom_text(
  inherit.aes = FALSE,
  data = aucData,
  mapping = aes(label = paste(model, "AUC: \n", round(auc, 3))),
  x = c(0.25, 0.15),
  y = c(0.4, 1.05)
)

From the graphs we can interpret that:

Model 1:

  • Its ROC curve is above the line of no discrimination, indicating that the model has some predictive capabilities

  • The AUC is 0.638, which is better than random guessing but suggests there’s room for improvement.

Model 2:

  • The ROC curve for Model 2 is significantly above that of Model 1, and much closer to the top-left corner, indicating better predictive performance.

  • The AUC is 0.899, which suggests a good classification performance, and it’s notably better than Model 1.

  • Its ability to discriminate between positive and negative classes is superior to that of Model 1.

Lastly, let us plot our influence plot.

# Influence Plot for Model 2
idCases <- car::influencePlot(model2)

The influence plot shows several data points with high leverage and large residuals, indicating potential outliers. Some observations, notably those labeled like “149,” have significant influence on the regression model due to their Cook’s D values.

Testing our model

Using our final model, we now turn to see how well this classifier does on our testing data. Recall that we initially set the test data during the train test split.

The confusion matrix below shows the performance of our model using the naïve decision rule.

# Set up testing data results
testingData <- testingData %>%
  mutate(
    gradNum = case_when(
      Status == 0 ~ 0,
      Status == 1 ~ 1
    ),
    .after = Status
  )
testingData$predict <- predict(
  object = model2,
  newdata = testingData,
  type = "response"
)
testingData <- testingData %>%
  mutate(
    model2Class = case_when(
      predict > 0.5 ~ "Censored",
      .default = "Deceased"
    )
  )
testingData$Status <- ifelse(testingData$Status == 1, "Censored", "Deceased")

# Build Confusion Matrix for Testing Data
testingData %>%
  tabyl(var1 = model2Class, var2 = Status) %>%
  adorn_title(
    placement = "combined",
    row_name = "Predicted",
    col_name = "Actual"
  ) %>%
  kable(
    caption = "Confusion Matrix for Test data"
  )
Confusion Matrix for Test data
Predicted/Actual Censored Deceased
Censored 30 13
Deceased 3 10

This shows that our model is has struggled with overfitting, with an especially low precision score. We will discuss this in the comparison with the tree model, but this is an importnat limitation of our data size as we discussed in the introduction. Lastly, we can plot the separation plot. We can see the separation has increased due to the over fitting we discussed.

#Sepeation Plot
par(mar = c(4,0,0,0))
separationplot(
  pred = testingData$predict, 
  actual = testingData$gradNum, 
  type = "rect",
  line = TRUE, 
  lwd2 = 2,
  show.expected = TRUE, 
  newplot = FALSE
)

#Comparison between models

Now let us compare the performance of our logistic regression model with a decision tree model. We will use the same train test split as before.

Part III: Exploring the sub-clusters of the cirrhosis data using unsupervised learning

In this section, we sought to if there are sub-collections of patients. We expect sub-collections to mimic medical definitions of the stages of Biliary Cirrhosis but are curious to see if clustering can reveal some newer observations.

Introduction

Biliary Cirrhosis patients are typically clustered in four main clusters (Source):

  • Stage 1: There’s inflammation and damage to the walls of medium-sized bile ducts.

  • Stage 2: There’s blockage of the small bile ducts.

  • Stage 3: This stage marks the beginning of scarring.

  • Stage 4: Cirrhosis has developed. This permanent, severe, scarring and damage to the liver.

In this section we will be using clustering on our data. K-means Clustering is an unsupervised learning technique where data points are grouped based on their similarities. It’s commonly used to identify patterns and structures within datasets without prior knowledge of the groups. The main advantage of clustering is its ability to discover hidden patterns in data. However, a significant drawback is the subjectivity in defining the ‘similarity’ criteria, which can lead to varying results and interpretations.

K-means clustering ([Source](https://medium.datadriveninvestor.com/k-means-clustering-4a700d4a4720))

An important motivation for this part of the study is explore how does “Stage” which is typically defined based on medical professionals’ observations compares to the data that we have in this data set.

Pre-processing the data

We had to pre-process the data for the best performance. First, we removed the ‘Stage’ column to avoid using outcome-related features in the unsupervised learning. The rows with N/A values were also removed. Lastly, we transformed various categorical variables into numeric formats, which is necessary for clustering algorithms.

#do the ml, take about observational stages vs now quantify, look into the different variables and what they mean.
cirrhosisCluster <- cirrhosis
cirrhosisCluster2 <- na.omit(cirrhosis)

cirrhosisCluster <- cirrhosisCluster %>%                 
                                  dplyr::select(-Stage)     
                                              
cirrhosisCluster <- na.omit(cirrhosisCluster) #cannot have NA values in clustering

cirrhosisCluster$Age_Years <- round(cirrhosisCluster$Age_Years) #round age to whole numbers


#reode sex... recode others later if need be #female is 0

#cirrhosisCluster  <- cirrhosisCluster %>%
#  select( -c(Status, Drug, Edema)) %>%
  
# mutate(Sex = ifelse(Sex == 'Female', 0, 1))
     
      
  cirrhosisCluster  <- cirrhosisCluster %>%
    mutate(Sex = ifelse(Sex == 'Female', 0, 1)) %>%
          mutate(Transplant = ifelse(Status == "CL", 1, 0)) %>%
              mutate(Status = ifelse(Status %in% c('C', 'CL'), 0 , 1)) %>%
                    mutate(Drug = ifelse(Drug == "D-penicillamine", 0, 1)) %>%
                        mutate(EdemaDiurectics = ifelse(Edema %in% c('S', 'Y'), 1, 0)) %>%
                              mutate(NoEdemaORD = ifelse(Edema == 'N' , 1, 0)) %>%
                                        mutate(EdemaANDD = ifelse(Edema == "Y", 1, 0)) %>%
                                                mutate(EdemaORD = ifelse(Edema == "S", 1, 0)) %>%
                                                                              dplyr::select(-Edema)
                                        
                                    
                          

#Data is already factored
     
#mutate(Sex = ifelse(Sex == 'Female', 0, 1)) %>%
  
   #mutate(Status = ifelse(Status %in% c('C', 'CL'), 0 , 1)) %>%
          #    mutate(Drug = ifelse(Drug == "D-penicillamine", 0, 1))

  
#???:
  
#make column yes no edema #then yes no under treatment

#same for transplant stuff
distCirrhosis <- dist(
  x = cirrhosisCluster,
  method = "euclidean"
)

Methodology

To apply the k-means algorithm. A mixed hierarchical and non-hierarchical was applied. This was done using the hkmeansm module, with k = 4 as our initial value. Based on the data properties, the euclidean (L2) distance works best.


hybridCirrhosis <- hkmeans(
  x = cirrhosisCluster,
  k = 4,
  hc.metric = "euclidean",
  hc.method = "ward.D",
  iter.max = 10
)

Results

We were able to visualize the results of the k-means algorithm. The first step was making a color palette and plotting the dendrogram tree.


StagesPalette <- c("#AA336A", "#770737", "#40B5AD", "#009E60", "#9FE2BF")
## MAKE A NEW PALLETE TO VISUALIZE
# Plot the initial dendrogram for hybrid approach ----
set.seed(380)
hkmeans_tree(
  hkmeans = hybridCirrhosis,
  rect.col = StagesPalette,
  cex = 0.4,
  main = "Initial Hierarchical Clusters"
)

As, you can see, the model was able to create clear clusters for the data. However, it is important to find the best value of k to get the optimal clusters. To do this we can use a scree plot. Here, we're looking for the number of clusters that corresponds to the "elbow".

# Create scree plot for choosing k ----
library(factoextra)
set.seed(380)
fviz_nbclust(
  x = cirrhosisCluster,
  diss = NULL,
  FUNcluster = kmeans,
  method = "wss",
  k.max = 10
)

NA
NA

From this, we identified that 5 was the ideal value of k, where the Total Within [Cluster] Sums of Squares begins leveling off. Let us create a new k-means model with k = 5. We can plot this refined model, with a format more easy to visualize.


hybridCirrhosis2 <- hkmeans(
  x = cirrhosisCluster,
  k = 5,
  hc.metric = "euclidean",
  hc.method = "ward.D",
  iter.max = 10
)
# Plot the final dendrogram for hybrid approach ----
# library(factoextra)
fviz_dend(
  x = hybridCirrhosis2,
  cex = 0.4,
  palette = StagesPalette,
  rect = FALSE,
  horiz = TRUE,
  repel = TRUE,
  main = "Final Dendrogram"
)

NA
NA
NA

As you can see, the 5 clusters were identified, which are color coded in our updated diagram.

Lastly, let us plot these clusters. Here is a plot of the initial model where k = 4

# Plot the final clustering for hybrid approach ----
# library(factoextra)
fviz_cluster(
  object = hybridCirrhosis,
  stand = FALSE,
  geom = "point",
  main = "Hybrid Cluster Plot - Initial model"
) +
  scale_color_manual(values = StagesPalette) +
  scale_fill_manual(values = StagesPalette) +
  theme_bw() 

We can also plot our refined model, as you can see the plot below identifies 5 clear clusters.


# Plot the final clustering for hybrid approach ----
# library(factoextra)
fviz_cluster(
  object = hybridCirrhosis2,
  stand = FALSE,
  geom = "point",
  main = "Hybrid Cluster Plot - Refined model"
) +
  scale_color_manual(values = StagesPalette) +
  scale_fill_manual(values = StagesPalette) +
  theme_bw() 

Now we can go back to our initial goal: comparing how the custers compare to the Stage variable

cirrhosisCluster2$cluster <- hybridCirrhosis$cluster
# Calculate mean (or median, etc.) for each variable in each cluster
library(dplyr)
cluster_summary <- cirrhosisCluster2 %>%
  group_by(cluster) %>%
  summarise_all(funs(mean(., na.rm = TRUE))) # Replace mean with median or any other function as necessary
Warning: `funs()` was deprecated in dplyr 0.8.0.
Please use a list of either functions or lambdas: 

  # Simple named list: 
  list(mean = mean, median = median)

  # Auto named with `tibble::lst()`: 
  tibble::lst(mean, median)

  # Using lambdas
  list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))Warning: There were 20 warnings in `summarise()`.
The first warning was:
ℹ In argument: `Status = mean(Status, na.rm = TRUE)`.
ℹ In group 1: `cluster = 1`.
Caused by warning in `mean.default()`:
! argument is not numeric or logical: returning NA
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 19 remaining warnings.
kable(cluster_summary %>% dplyr::select(-Status, -Drug, - Sex, -Edema, -Stage), label = "Cluster Summary")
cluster N_Days Age Ascites Hepatomegaly Spiders Bilirubin Cholesterol Albumin Copper Alk_Phos SGOT Tryglicerides Platelets Prothrombin Age_Years
1 1475.862 23503.95 0.1724138 0.5517241 0.3103448 4.034483 327.0862 3.337069 107.77586 1492.241 120.0178 126.7414 248.3276 11.15517 64.35030
2 2579.950 18505.65 0.1000000 0.6500000 0.3500000 4.275000 376.3500 3.372000 149.15000 8394.200 136.8590 145.4000 263.0500 10.98500 50.66571
3 2074.659 13933.03 0.0227273 0.4204545 0.2954545 2.842045 426.8977 3.629773 93.68182 1582.877 130.9384 119.4205 281.6591 10.46591 38.14657
4 2058.918 18734.59 0.0454545 0.5454545 0.2636364 3.186364 349.1182 3.547546 93.94545 1430.342 118.5102 124.7818 252.7182 10.68455 51.29251
ggplot(cirrhosisCluster2, aes(x = factor(cluster), fill = factor(Stage))) +
  geom_bar(position = "dodge") +
  labs(title = "Distribution of Stages within Clusters",
       x = "Cluster",
       y = "Count",
       fill = "Stage") +
  theme_minimal()

We can see that the clusters are not evenly distributed. This is a good sign, as it means that the clusters are not just a random grouping of the data.

Discussion

Author Contributions

References–Citation style is your choice, but all sources should be documented (both in text and in the References section). This includes where you got your data.

Code Appendix

LS0tCnRpdGxlOiAiRmluYWwgUHJvamVjdCBSZXBvcnQ6IEFuIGV4cGxvcmF0aW9uIG9mIEJpbGlhcnkgQ2lycmhvc2lzIGFuZCBUcmVhdG1lbnRzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yczogIk1paGlyIEt1bGthcm5pLCBOaXRoaWthIE1lbm9uIgotLS0KCiMgQmFja2dyb3VuZAoKIyMgV2hhdCBpcyBCaWxpYXJ5IENob2xhbmdpdGlzPwoKUHJpbWFyeSBCaWxpYXJ5IENob2xhbmdpdGlzIChwcmV2aW91c2x5IGNhbGxlZCBQcmltYXJ5IEJpbGlhcnkgQ2lycmhvc2lzKSBpcyBhbiBhdXRvaW1tdW5lIGRpc2Vhc2Ugd2hlcmUgYmlsZSBkdWN0cyBiZWNvbWUgc3dvbGxlbiBhbmQgaW5mbGFtZWQgYW5kIGJsb2NrIHRoZSBmbG93IG9mIGJpbGUuIEJpbGUgaXMgYSBzdWJzdGFuY2UgdGhhdCBhaWRzIHdpdGggZGlnZXN0aW9uLiBUaGUgYmlsZSBkdWN0cyBjYXJyeSBiaWxlIGZyb20gdGhlIGxpdmVyIHRvIHRoZSBzbWFsbCBpbnRlc3RpbmUuIFRoZSBzd2VsbGluZyBhbmQgaW5mbGFtbWF0aW9uIGNhbiBsZWFkIHRvIHNjYXJyaW5nIG9mIHRoZSBsaXZlciB3aGljaCBpcyBjaXJyaG9zaXMuIEFkdmFuY2VkIGNpcnJob3NpcyBjYW4gbGVhZCB0byBsaXZlciBmYWlsdXJlIG9yIGxpdmVyIGNhbmNlci4gTWVkaWNhdGlvbiBjYW4gc2xvdyBwcm9ncmVzc2lvbi4gVGhlcmUgaXMgbm8gZGVmaW5pdGUgY3VyZSBhdCB0aGlzIHRpbWUuCgohW1tQcmltYXJ5IEJpbGlhcnkgQ2hvbGFuZ2l0aXMgKFtTb3VyY2VdKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vc2VhcmNoP2NsaWVudD1zYWZhcmkmc2NhX2Vzdj01OTEyNzYzODYmcmxzPWVuJnN4c3JmPUFNOUhrS210SlZPQzJKcmYyQlNZVkIyTy1RY25UU2d4Nmc6MTcwMjY2NDcwNTEwMCZxPVByaW1hcnkrQmlsaWFyeStDaG9sYW5naXRpcyZ0Ym09aXNjaCZzb3VyY2U9bG5tcyZzYT1YJnZlZD0yYWhVS0V3aWQwcXluaUpLREF4WDlGRmtGSFZIM0Njb1EwcFFKZWdRSUR4QUImYml3PTE0NDAmYmloPTg0OCZkcHI9MiNpbWdyYz1VZVZfZmhQLTh2UE9sTSkpXXsudW5kZXJsaW5lfV0oMTc3MTUtcHJpbWFyeS1iaWxpYXJ5LWNob2xhbmdpdGlzLndlYnApCgojIyBXaHkgaXMgdGhlIGRhdGEgaW1wb3J0YW50IHRvIHVzPwoKQm90aCBvZiB1cyBoYXZlIGJlZW4gdmVyeSBpbnRlcmV0c3RlZCBpbiB0aGUgaW50ZXJzZWN0aW9uIG9mIG1lZGljaW5lIGFuZCBtYWNoaW5lIGxlYXJuaW5nLiBUaGUgaWRlYSB0aGF0IHRoZSBjb25jZXB0cyB3ZSBhcmUgbGVhcm5pbmcgaW4gb3VyIERTIG1ham9ycyBjb3VsZCBoYXZlIHN1Y2ggYW4gaW1wYWN0IG9uIHRoZSBoZWFsdGggaW5kdXN0cnkgd2FzIHJlYWxseSBzcGVjaWFsLiBBbm90aGVyIGltcG9ydGFudCBhc3BlY3Qgd2FzIHRoYXQgdGhlcmUgaXMgc28gbXVjaCBwb3RlbnRpYWwgZm9yIGFkdmFuY2VtZW50IG9mIG1lZGljaW5lLCB3ZSBib3RoIGFncmVlZCB0aGF0IHRoaXMgYXJlYSBpcyBNTHMgbW9zdCBwcm9taXNpbmcgYXNwZWN0LiBUaGUgY2lycmhvc2lzIGRhdGEgc2V0IHBlcnRpY3VsYXJseSBzdG9vZCBvdXQgdG8gdXMgYmVjYXVzZSB0aGUgZGF0YSBzZXQgaW5jbHVkZWQgc28gbWFueSBpbnRlcmVzdGluZyBhdHRyaWJ1dGVzIGFuZCB3ZSBhbnRlZCB0byBzZWUgaG93IHRoZXkgYXJlIGNvbm5lY3RlZCB3aXRoIHRoZSBvdXRjb21lcyBvZiB0aGUgcGF0aWVudC4KClRoZSBkYXRhIHNldCBhbHNvIGluY2x1ZGVzIHNvbWUgaW1wb3J0YW50IGxpbWl0YXRpb25zLiBNb3N0IGltcG9ydGFudGx5LCB0aGVyZSBhcmUgb25seSA0MTggY2FzZXMsIGdyZWF0bHkgYWZmZWN0aW5nIHRoZSB0eXBlcyBvZiBhbmFseXNpcyB3ZSBjb3VsZCBkby4gVGhpcyBzaG93cyByZWFsbSB3b3JsZCBwcm9ibGVtcyBkYXRhIHNjaWVudGlzdHMgZmFjZS4gVGh1cywgb3VyIGdvYWwgaXMgdG8gdXNlIG91ciBTdGF0aXN0aWNhbCBEUyBza2lsbHNldCB0byBhcHByb2FjaCB0aGlzIHByb2JsZW0sIGRlc3BpdGUgdGhlIGxpbWl0YXRpb25zLgoKIyMgT2JqZWN0aXZlcwoKRXhwbG9yaW5nIHRoZSBzdHVkeSwgd2UgY29uY2x1ZGVkIG9uIHR3byBjb3JlIHF1ZXN0aW9ucyB3ZSBob3BlIHRvIGFkZHJlc3MgaW4gdGhpcyByZXBvcnQ6CgojIyMgQ1JRMTogV2hhdCBwcmVkaWN0b3JzLCBpZiBhbnkgYWZmZWN0ZWQgdGhlIHN0YXR1cyBvZiB0aGUgcGF0aWVudCBhdCB0aGUgZW5kIG9mIHRoZSBzdHVkeT8KCiMjIyBDUlEyOiBBcmUgdGhlcmUgZmFjdG9ycyBub3QgY3VycmVudGx5IGluY2x1ZGVkIGluIG1lZGljYWwgZGVmaW5pdGlvbiB0aGF0IGNhbiBoZWxwIGRpc3Rpbmd1aXNoIGRpZmZlcmVudCBzdGFnZXMgb2YgQmlsaWFyeSBDaG9sYW5naXRpcz8KCiMjIENsaW5pY2FsIFN0dWR5ICsgRGF0YXNldCBCYWNrZ3JvdW5kIDoKClRoZSBkYXRhc2V0IGlzIHByb2R1Y2VkIGZyb20gYSBjbGluaWNhbCBzdHVkeSBieSBNYXlvIENsaW5pYyBydW4gZnJvbSAxOTc0LTE5ODQuIFRoZSBmaW5hbCAnU3RhdHVzJyBvZiBlYWNoIHBhdGllbnQgd2FzIG9ic2VydmVkIGluIDE5ODYuICdTdGF0dXMnIHdhcyBlaXRoZXIgRGVhZCwgQ2Vuc29yZWQsIG9yIENlbnNvcmVkIGR1ZSB0byBsaXZlciB0cmFuc3BsYW50LiBBIGxpbmsgdG8gdGhlIG9yaWdpbmFsIHN0dWR5IGlzIGhlcmU6IDxodHRwczovL2ZhY3VsdHkud2FzaGluZ3Rvbi5lZHUvYWJhbnNhbC9TaG9ydENvdXJzZV9EeW5hbWljRGVjaXNpb25NYWtpbmcvRGlja3NvbjE5ODlfTWF5b1BCQ09yaWdpbmFsQXJ0aWNsZS5wZGY+CgpUaGUgZGF0YSBjb250YWlucyB0aGUgZm9sbG93aW5nIGF0dHJpYnV0ZXMgKFtEYXRhIFNvdXJjZV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYXRhc2V0cy9mZWRlc29yaWFuby9jaXJyaG9zaXMtcHJlZGljdGlvbi1kYXRhc2V0L2RhdGEpKToKCjEpICoqSUQqKjogdW5pcXVlIGlkZW50aWZpZXJcCjIpICoqTl9EYXlzKio6IG51bWJlciBvZiBkYXlzIGJldHdlZW4gcmVnaXN0cmF0aW9uIGFuZCB0aGUgZWFybGllciBvZiBkZWF0aCwgdHJhbnNwbGFudGF0aW9uLCBvciBzdHVkeSBhbmFseXNpcyB0aW1lIGluIEp1bHkgMTk4NlwKMykgKipTdGF0dXMqKjogc3RhdHVzIG9mIHRoZSBwYXRpZW50IEMgKGNlbnNvcmVkKSwgQ0wgKGNlbnNvcmVkIGR1ZSB0byBsaXZlciB0eCksIG9yIEQgKGRlYXRoKVwKNCkgKipEcnVnKio6IHR5cGUgb2YgZHJ1ZyBELXBlbmljaWxsYW1pbmUgb3IgcGxhY2Vib1wKNSkgKipBZ2UqKjogYWdlIGluIFtkYXlzXVwKNikgKipTZXgqKjogTSAobWFsZSkgb3IgRiAoZmVtYWxlKVwKNykgKipBc2NpdGVzKio6IHByZXNlbmNlIG9mIGFzY2l0ZXMgTiAoTm8pIG9yIFkgKFllcylcCjgpICoqSGVwYXRvbWVnYWx5Kio6IHByZXNlbmNlIG9mIGhlcGF0b21lZ2FseSBOIChObykgb3IgWSAoWWVzKVwKOSkgKipTcGlkZXJzKio6IHByZXNlbmNlIG9mIHNwaWRlcnMgTiAoTm8pIG9yIFkgKFllcylcCjEwKSAqKkVkZW1hKio6IHByZXNlbmNlIG9mIGVkZW1hIE4gKG5vIGVkZW1hIGFuZCBubyBkaXVyZXRpYyB0aGVyYXB5IGZvciBlZGVtYSksIFMgKGVkZW1hIHByZXNlbnQgd2l0aG91dCBkaXVyZXRpY3MsIG9yIGVkZW1hIHJlc29sdmVkIGJ5IGRpdXJldGljcyksIG9yIFkgKGVkZW1hIGRlc3BpdGUgZGl1cmV0aWMgdGhlcmFweSlcCjExKSAqKkJpbGlydWJpbioqOiBzZXJ1bSBiaWxpcnViaW4gaW4gW21nL2RsXVwKMTIpICoqQ2hvbGVzdGVyb2wqKjogc2VydW0gY2hvbGVzdGVyb2wgaW4gW21nL2RsXVwKMTMpICoqQWxidW1pbioqOiBhbGJ1bWluIGluIFtnbS9kbF1cCjE0KSAqKkNvcHBlcioqOiB1cmluZSBjb3BwZXIgaW4gW3VnL2RheV1cCjE1KSAqKkFsa19QaG9zKio6IGFsa2FsaW5lIHBob3NwaGF0YXNlIGluIFtVL2xpdGVyXVwKMTYpICoqU0dPVCoqOiBTR09UIGluIFtVL21sXVwKMTcpICoqVHJpZ2x5Y2VyaWRlcyoqOiB0cmlnbGljZXJpZGVzIGluIFttZy9kbF1cCjE4KSAqKlBsYXRlbGV0cyoqOiBwbGF0ZWxldHMgcGVyIGN1YmljIFttbC8xMDAwXVwKMTkpICoqUHJvdGhyb21iaW4qKjogcHJvdGhyb21iaW4gdGltZSBpbiBzZWNvbmRzIFtzXVwKMjApICoqU3RhZ2UqKjogaGlzdG9sb2dpYyBzdGFnZSBvZiBkaXNlYXNlICgxLCAyLCAzLCBvciA0KQoKRm9yIHRoZSBiYXNpYyBwcmUtcHJvY2Vzc2luZywgd2UgYmluYXJ5LWVuY29kZWQgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBzZXQgYW5kIHNldCB0aGVtIGFzIGZhY3RvcnMuCgpgYGB7ciBsb2FkIHBhY2thZ2VzfQoKbGlicmFyeShzdXJ2aXZhbCkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHN1cnZpdmFsKQpsaWJyYXJ5KHN1cnZtaW5lcikKbGlicmFyeShrYWJsZUV4dHJhKQpsaWJyYXJ5KGZhY3RvZXh0cmEpCgpgYGAKCmBgYHtyfQojbG9hZCBpbiB0aGUgZGF0YQpsaWJyYXJ5KHRpZHl2ZXJzZSkKI2xvYWQgZGF0YQpjaXJyaG9zaXMgPC0gcmVhZF9jc3YoImNpcnJob3Npcy5jc3YiKQpgYGAKCmBgYHtyfQojZW5jb2RlIHRoZSBzZXgsIGFzY2l0ZXMsIGhlcGF0b21lZ2FseSwgc3BpZGVycywgZWRlbWEgaW50byBiaW5hcnkgdmFyaWFibGVzCmNpcnJob3NpcyRTZXggPC0gaWZlbHNlKGNpcnJob3NpcyRTZXggPT0gIkYiLCAwLCAxKQpjaXJyaG9zaXMkQXNjaXRlcyA8LSBpZmVsc2UoY2lycmhvc2lzJEFzY2l0ZXMgPT0gIlkiLCAxLCAwKQpjaXJyaG9zaXMkSGVwYXRvbWVnYWx5IDwtIGlmZWxzZShjaXJyaG9zaXMkSGVwYXRvbWVnYWx5ID09ICJZIiwgMSwgMCkKY2lycmhvc2lzJFNwaWRlcnMgPC0gaWZlbHNlKGNpcnJob3NpcyRTcGlkZXJzID09ICJZIiwgMSwgMCkKI2ZhY3Rvcml6ZSB0aGUgc3RhZ2UKY2lycmhvc2lzJFN0YWdlIDwtIGZhY3RvcihjaXJyaG9zaXMkU3RhZ2UpCmNpcnJob3NpcyRTdGF0dXMgPC0gZmFjdG9yKGNpcnJob3NpcyRTdGF0dXMpCmNpcnJob3NpcyREcnVnIDwtIGZhY3RvcihjaXJyaG9zaXMkRHJ1ZykKY2lycmhvc2lzJFNleCA8LSBmYWN0b3IoY2lycmhvc2lzJFNleCkKI2NpcnJob3NpcyRBc2NpdGVzIDwtIGZhY3RvcihjaXJyaG9zaXMkQXNjaXRlcykKI2NpcnJob3NpcyRIZXBhdG9tZWdhbHkgPC0gZmFjdG9yKGNpcnJob3NpcyRIZXBhdG9tZWdhbHkpCiNjaXJyaG9zaXMkU3BpZGVycyA8LSBmYWN0b3IoY2lycmhvc2lzJFNwaWRlcnMpCmNpcnJob3NpcyRFZGVtYSA8LSBmYWN0b3IoY2lycmhvc2lzJEVkZW1hKQpjaXJyaG9zaXMkU2V4IDwtIGZhY3RvcihpZmVsc2UoY2lycmhvc2lzJFNleCA9PSAwLCAiRmVtYWxlIiwgIk1hbGUiKSkKCiNEcm9wIHRoZSBmaXJzdCBjb2x1bW4KY2lycmhvc2lzIDwtIGNpcnJob3Npc1ssLTFdCmBgYAoKCmBgYHtyfQojPz8/Pz8KY2lycmhvc2lzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBOX0RheXMpKSArIGdlb21faGlzdG9ncmFtKCkgKwogIGZhY2V0X3dyYXAoZmFjZXRzID0gdmFycyhTdGF0dXMpKQoKYGBgCgpgYGB7cn0KI1JlcXVpcmVkIGxpYnJhcmllcwpsaWJyYXJ5KHN1cnZpdmFsKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoc3Vydml2YWwpCmxpYnJhcnkoc3Vydm1pbmVyKQoKYGBgCgojIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMKCiMjIFBhcnQgMTogRXhwbG9yaW5nIHRoZSBkZW1vZ3JhcGhpY3Mgb2YgdGhlIHBhdGllbnRzCgojIyMgU2V4IFJhdGlvIG9mIFBhdGllbnRzCgpgYGB7cn0KCiNjaXJyaG9zaXMkU2V4IDwtIGZhY3RvcihpZmVsc2UoY2lycmhvc2lzJFNleCA9PSAwLCAiRmVtYWxlIiwgIk1hbGUiKSkKCiMgUHJlcGFyaW5nIHRoZSBkYXRhIGZvciBwaWUgY2hhcnQKc2V4X2RhdGEgPC0gY2lycmhvc2lzICU+JSAKICAgICAgICAgICAgY291bnQoU2V4KSAlPiUgCiAgICAgICAgICAgIG11dGF0ZShQZXJjZW50YWdlID0gbiAvIHN1bShuKSAqIDEwMCkKCiMgUGllIENoYXJ0CmdncGxvdChzZXhfZGF0YSwgYWVzKHg9IiIsIHk9UGVyY2VudGFnZSwgZmlsbD1TZXgpKSArCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHdpZHRoPTEpICsKICAgIGNvb3JkX3BvbGFyKCJ5Iiwgc3RhcnQ9MCkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iUGFzdGVsMSIpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xNCwgZmFjZT0iYm9sZCIpKSArCiAgICBsYWJzKGZpbGw9IlNleCIsIHRpdGxlPSIgU2V4IEFtb25nc3QgUGF0aWVudHMiKQoKCgoKYGBgCgpQcmltYXJ5IEJpbGlhcnkgQ2hvbGFuZ2l0aXMgYWZmZWN0cyB3b21lbiBtdWNoIG1vcmUgdGhhbiBtZW4gYXQgYSAxMC0xIHJhdGlvLiBUaGUgZGF0YSBvZiB0aGlzIGNsaW5pY2FsIHN0dWR5IG1pbWljcyB0aGUgZ3JlYXRlciBwb3B1bGF0aW9uLiBJbnZlc3RpZ2F0aW5nIGdlbmRlci1iYXNlZCBkaWZmZXJlbmNlcyBpbiBkaXNlYXNlIHByb2dyZXNzaW9uIGNhbiB1bmNvdmVyIGFueSBnZW5kZXItc3BlY2lmaWMgcGF0dGVybnMgaW4gUEJDLiBUaGlzIGNvdWxkIGxlYWQgdG8gZ2VuZGVyLXRhaWxvcmVkIHRyZWF0bWVudCBhcHByb2FjaGVzIGFuZCBhIGJldHRlciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBkaXNlYXNlJ3MgYmlvbG9neSwgd2hpY2ggbWlnaHQgZGlmZmVyIGJldHdlZW4gbWFsZXMgYW5kIGZlbWFsZXMuCgojIyMgQWdlIERpc3RyaWJ1dGlvbiBvZiBQYXRpZW50cwoKYGBge3J9CiMgQ29udmVydGluZyBhZ2UgZnJvbSBkYXlzIHRvIHllYXJzIGZvciBiZXR0ZXIgcmVhZGFiaWxpdHkKY2lycmhvc2lzJEFnZV9ZZWFycyA8LSBjaXJyaG9zaXMkQWdlIC8gMzY1LjI1CgojIEhpc3RvZ3JhbQpnZ3Bsb3QoY2lycmhvc2lzLCBhZXMoeD1BZ2VfWWVhcnMpKSArCiAgICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aD01LCBmaWxsPSIjNjliM2EyIiwgY29sb3I9IiNlOWVjZWYiKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgbGFicyh0aXRsZT0iQWdlIERpc3RyaWJ1dGlvbiBvZiBQQkMgUGF0aWVudHMiLCB4PSJBZ2UgKHllYXJzKSIsIHk9IkNvdW50IikgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTE0LCBmYWNlPSJib2xkIikpCgpgYGAKClRoZSBkaXN0cmlidXRpb24gb2YgYWdlcyBpcyBxdWl0ZSBub3JtYWwuCgojIyBQYXJ0IDI6IExvb2tpbmcgYXQgdGhlIGRhdGEgaXRzZWxmCgojIyMgTkEgY291bnRzIGFtb25nc3Qgb3VyIHZhcmlhYmxlcwoKYGBge3J9CmNvdW50IDwtICBkYXRhLmZyYW1lKE5BX0NvdW50c1BlclZhciA9IGNvbFN1bXMoaXMubmEoY2lycmhvc2lzKSkpCmthYmxlKGNvdW50KQpgYGAKCkFuIG9ic2VydmF0aW9uIGlzIHRoYXQgbWFueSBvZiB0aGUgdmFyaWFibGVzIGhhdmUgMTA2IE5Bcy4gVGhpcyBpbmRpY2F0ZXMgdGhhdCBhIGdvb2QgZnJhY3Rpb24gWzEwNiBwYXRpZW50c10gY291bGQgaGF2ZSBiZWVuIGVxdWFsbHkgdHJhY2tlZCBhbmQgbWVhc3VyZWQgaW4gYSBsZXNzIGV4dGVuc2l2ZSB3YXkuCgojIyBEaXN0cmlidXRpb24gb2YgU3RhZ2VzIG9mIEJpbGlhcnkgQ2hvbGFuZ2l0aXMKCmBgYHtyfQoKY2lycmhvc2lzICU+JQogICAgICBnZ3Bsb3QoYWVzKHggPSBTdGFnZSkpICsgZ2VvbV9iYXIoZmlsbCA9ICIjMDk3OTY5IikgIyBuZWVkIGNvbG9yCgpgYGAKClRoaXMgZGlzdHJpYnV0aW9uIGlzIGxlZnQgc2tld2VkIGFuZCBub3Qgc3ltbWV0cmljLiBNb3N0IHBhdGllbnRzIGhhdmUgc3RhZ2UgMyBhbmQgdGhlbiA0IG9mIEJpbGlhcnkgQ2hvbGFuZ2l0aXMuCgojIyBQYXJ0IDM6IEV4cGxvcmluZyBzb21lIGludGVyYWN0aW9ucyBvZiB2YXJpYWJsZXMKCiMjIyBIb3cgZG9lcyBwcmVzY2Vuc2Ugb2YgYSBELXBlbmljaWxsYW1pbmUgd2l0aCBzdGFnZSBvZiBCaWxpYXJ5IENob2xhbmdpdGlzIGltcGFjdCBudW1iZXIgb2YgZGF5cyB0aWxsIGRlYXRoPwoKYGBge3J9CgpnZ3Bsb3QoZGF0YSA9IGNpcnJob3NpcywgYWVzIChEcnVnLCBTdGFnZSwgZmlsbCA9IE5fRGF5cykpKyBnZW9tX3RpbGUoKSArIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiUmRQdSIsIHRyYW5zID0gInJldmVyc2UiKSAKCmBgYAoKVGhpcyBoZWF0IG1hcCBzaG93cyB0aGF0IGJlaW5nIGEgcGxhY2VibyBpbiBzdGFnZSAxIGdpdmVzIHlvdSBhIGdyZWF0ZXIgYW1vdW50IG9mIGRheXMgdGlsbCBkZWF0aCBpbiB0aGlzIHNhbXBsZSBvZiBwYXRpZW50cy4KCiMjIyBJbmRpdmlkdWFscyB3aXRoIEVkZW1hLCBhbmQgaWYgdGhleSB3ZXJlIG9uIHRoZSBkcnVnCgpgYGB7cn0KCmhpID0gYygiIzQwQjVBRCIsICIjMDA5RTYwIiwgIiM5RkUyQkYiKQpsaWJyYXJ5KGdnbW9zYWljKQpjaXJyaG9zaXMgJT4lCiAgZ2dwbG90KCkgKwogIGdlb21fbW9zYWljKGFlcyggeCA9IHByb2R1Y3QoRWRlbWEpLCBmaWxsID0gRHJ1ZykpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gaGkpCmBgYAoKRWRlbWEgaXMgc3dlbGxpbmcgZHVlIHRvIHRvbyBtdWNoIGxpcXVpZCB0cmFwcGVkIGluIHRoZSBib2R5J3MgdGlzc3Vlcy4gSXQncyBjb21tb24gY29tcGxpY2F0aW9uIGluIGxpdmVyIGRpc2Vhc2VzIGFuZCBjYW4gc2lnbmlmaWNhbnRseSBpbXBhY3QgcGF0aWVudCBxdWFsaXR5IG9mIGxpZmUgYW5kIHN1cnZpdmFsLiBVbmRlcnN0YW5kaW5nIGhvdyBkaWZmZXJlbnQgbGV2ZWxzIG9mIGVkZW1hIChub25lLCBjb250cm9sbGVkIGJ5IGRpdXJldGljcywgb3IgcGVyc2lzdGVudCBkZXNwaXRlIHRyZWF0bWVudCkgYWZmZWN0IHN1cnZpdmFsIGNhbiBpbmZvcm0gcGF0aWVudCBtYW5hZ2VtZW50IHN0cmF0ZWdpZXMgYW5kIGhpZ2hsaWdodCB0aGUgbmVlZCBmb3IgYWdncmVzc2l2ZSBpbnRlcnZlbnRpb25zIGluIGNlcnRhaW4gY2FzZXMuIEZyb20gdGhlIGdyYXBoIHdlIGNhbiBzZWUgYSBldmVuIHNwbGl0IGJldHdlZW4gdGhlIHBsYWNlYm8gYW5kIGRydWcgZm9yIEVkZW1hIHN0YXR1cy4gVGhlcmUgaXMgbm8gTkEgdmFsdWVzIGZvciBFZGVtYSBwZXJzaXN0ZW50IGRlc3BpdGUgZGl1cmV0aWNzLiBXZSBjYW5ub3QgZGlyZWN0bHkgY29tcGFyZSBpdCB0aGUgc3BsaXRzIGZvciBvdGhlciBzdGF0dXNlcyBnaXZlbiB0aGVpciBOQSB2YWx1ZXMgaWYga25vd24gY291bGQgY2hhbmdlIHRoZSBsb29rIG9mIHRoZSBncmFwaC4KCiMjIyBTdXJ2aXZhbCBBbmFseXNpcyBCYXNlZCBvbiBUcmVhdG1lbnQgYW5kIFN0YWdlOiBIb3cgZG9lcyB0aGUgc3Vydml2YWwgcmF0ZSBkaWZmZXIgYW1vbmcgcGF0aWVudHMgYXQgZGlmZmVyZW50IHN0YWdlcyBvZiBQQkMgd2hvIHJlY2VpdmVkIEQtcGVuaWNpbGxhbWluZSB2ZXJzdXMgcGxhY2Vibz8KClRoaXMgcXVlc3Rpb24gaW52ZXN0aWdhdGVzIHRoZSBlZmZlY3RpdmVuZXNzIG9mIHRoZSBkcnVnIEQtcGVuaWNpbGxhbWluZSBjb21wYXJlZCB0byBhIHBsYWNlYm8sIGNvbnNpZGVyaW5nIHRoZSBzdGFnZSBvZiBQQkMuIEJ5IGFuYWx5emluZyBzdXJ2aXZhbCByYXRlcyBhY3Jvc3MgZGlmZmVyZW50IGRpc2Vhc2Ugc3RhZ2VzIGFuZCB0cmVhdG1lbnQgdHlwZXMsIHdlIGNhbiBhc3Nlc3MgdGhlIGRydWcncyBlZmZlY3RpdmVuZXNzIGF0IHZhcmlvdXMgZGlzZWFzZSBzdGFnZXMuIFRoaXMgaW5mb3JtYXRpb24gaXMgdml0YWwgZm9yIGNsaW5pY2lhbnMgdG8gbWFrZSBpbmZvcm1lZCBkZWNpc2lvbnMgYWJvdXQgdHJlYXRtZW50IHBsYW5zIGFuZCBmb3IgcmVzZWFyY2hlcnMgdG8gdW5kZXJzdGFuZCB0aGUgZHJ1ZydzIGltcGFjdC4KCmBgYHtyfQpzdXJ2X29iamVjdCA8LSBTdXJ2KGNpcnJob3NpcyROX0RheXMsIGNpcnJob3NpcyRTdGF0dXMgPT0gJ0QnKQpzdXJ2X2ZpdCA8LSBzdXJ2Zml0KHN1cnZfb2JqZWN0IH4gY2lycmhvc2lzJERydWcgKyBjaXJyaG9zaXMkU3RhZ2UsIGRhdGEgPSBjaXJyaG9zaXMpCmdnc3VydnBsb3Qoc3Vydl9maXQsIAogICAgICAgICAgIGRhdGEgPSBjaXJyaG9zaXMsCiAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFLCAKICAgICAgICAgICAjcGFsZXR0ZSA9IGMoIiMwMEFGQkIiLCAiI0U3QjgwMCIsICIjRkM0RTA3IiksCiAgICAgICAgICAgeGxhYiA9ICJEYXlzIiwgCiAgICAgICAgICAgeWxhYiA9ICJTdXJ2aXZhbCBQcm9iYWJpbGl0eSIsIAogICAgICAgICAgIHRpdGxlID0gIlN1cnZpdmFsIEN1cnZlcyBieSBUcmVhdG1lbnQgYW5kIFN0YWdlIikKYGBgCgojIyMgSW1wYWN0IG9mIExpdmVyIENvbXBsaWNhdGlvbnMgb24gU3Vydml2YWw6IEhvdyBkbyB0aGUgcHJlc2VuY2Ugb2YgYXNjaXRlcywgaGVwYXRvbWVnYWx5LCBhbmQgc3BpZGVycyByZWxhdGUgdG8gc3Vydml2YWwgdGltZT8KCkV4cGxvcmluZyBob3cgbGl2ZXItcmVsYXRlZCBzeW1wdG9tcyAoYXNjaXRlcywgaGVwYXRvbWVnYWx5LCBhbmQgc3BpZGVycykgYWZmZWN0IHBhdGllbnQgc3Vydml2YWwgcHJvdmlkZXMgaW5zaWdodHMgaW50byB0aGUgc2V2ZXJpdHkgb2YgdGhlc2UgY29tcGxpY2F0aW9ucyBpbiBQQkMgcHJvZ3Jlc3Npb24uIFRoaXMgY2FuIGhlbHAgaW4gaWRlbnRpZnlpbmcgaGlnaC1yaXNrIHBhdGllbnRzIGFuZCB1bmRlcnN0YW5kaW5nIHRoZSBkaXNlYXNlJ3MgaW1wYWN0IG9uIGxpdmVyIGZ1bmN0aW9uLgoKYGBge3J9CnN1cnZfZml0X2NvbXBsaWNhdGlvbnMgPC0gc3VydmZpdChzdXJ2X29iamVjdCB+IGNpcnJob3NpcyRBc2NpdGVzICsgY2lycmhvc2lzJEhlcGF0b21lZ2FseSArIGNpcnJob3NpcyRTcGlkZXJzLCBkYXRhID0gY2lycmhvc2lzKQpnZ3N1cnZwbG90KHN1cnZfZml0X2NvbXBsaWNhdGlvbnMsIAogICAgICAgICAgIGRhdGEgPSBjaXJyaG9zaXMsCiAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFLCAKICAgICAgICAgICAjcGFsZXR0ZSA9IGMoIiMyRTlGREYiLCAiI0ZDNEUwNyIsICIjNkFDQzY1IiksCiAgICAgICAgICAgeGxhYiA9ICJEYXlzIiwgCiAgICAgICAgICAgeWxhYiA9ICJTdXJ2aXZhbCBQcm9iYWJpbGl0eSIsIAogICAgICAgICAgIHRpdGxlID0gIlN1cnZpdmFsIEN1cnZlcyBieSBMaXZlciBDb21wbGljYXRpb25zIikKYGBgCgojIyMgQ29ycmVsYXRpb24gYmV0d2VlbiBCaW9jaGVtaWNhbCBNYXJrZXJzIGFuZCBEaXNlYXNlIFN0YWdlOiBXaGF0IGlzIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGJpb2NoZW1pY2FsIG1hcmtlcnMgKEJpbGlydWJpbiwgQ2hvbGVzdGVyb2wsIEFsYnVtaW4sIENvcHBlciwgQWxrX1Bob3MsIFNHT1QsIFRyaWdseWNlcmlkZXMpIGFuZCB0aGUgaGlzdG9sb2dpYyBzdGFnZSBvZiB0aGUgZGlzZWFzZT8KClRoaXMgYW5hbHlzaXMgaXMgY3J1Y2lhbCB0byB1bmRlcnN0YW5kIGhvdyBkaWZmZXJlbnQgYmlvY2hlbWljYWwgbWFya2VycyBjb3JyZWxhdGUgd2l0aCB0aGUgZGlzZWFzZSdzIHByb2dyZXNzaW9uLiBTdWNoIGNvcnJlbGF0aW9ucyBjYW4gYWlkIGluIHRoZSBlYXJseSBkZXRlY3Rpb24gb2YgZGlzZWFzZSBzZXZlcml0eSwgaGVscCBpbiBtb25pdG9yaW5nIHRoZSBkaXNlYXNlIHByb2dyZXNzaW9uLCBhbmQgcG90ZW50aWFsbHkgZ3VpZGUgdHJlYXRtZW50IGFkanVzdG1lbnRzLgoKIyBQYXJ0IEk6IENsYXNzaWZpY2F0aW9uIEFsZ29yaXRobSBmb3IgdGhlIFN0YXR1cyBvZiB0aGUgUGF0aWVudCB1c2luZyBDQVJUCgpUbyBhZGRyZXNzIHRoZSBmaXJzdCBzdWItZ29hbCBvZiB0aGlzIHByb2plY3QsIHdlIHdpbGwgYmUgZXhwbG9yaW5nIHRoZSBwcmVkaWN0aW9uIG9mIHRoZSBzdGF0dXMgb2YgYSBwYXRpZW50IGF0IHRoZSBlbmQgb2YgdGhlIHN0dWR5LiBPdXIgbWFpbiBvYmplY3RpdmUgdGhyb3VnaCB0aGlzIGlzIHRvIGdhaW4gYSBzdHJvbmdlciB1bmRlcnN0YW5kaW5nIG9mIHdoYXQgZmFjdG9ycyBwbGF5ZWQgaW4gdGhlIHJvbGUgb2YgdGhlIGRlYXRoIG9mIHRoZSBwYXRpZW50LiBUbyBkbyB0aGlzLCB3ZSB3aWxsIGJlIHVzaW5nIERlY2lzaW9uIFRyZWVzLiBMZXQgdXMgc3RhcnQgYnkgZXhwbG9yaW5nIHdoYXQgdGhlc2UgYXJlIGFuZCB3aHkgd2UgY2hvc2UgdG8gdXNlIHRoZW0uCgojIyBJbnRyb2R1Y3Rpb24KCkRlY2lzaW9uIHRyZWVzIGFyZSBhIHR5cGUgb2YgbW9kZWwgdXNlZCBpbiBzdGF0aXN0aWNzIGZvciBtYWtpbmcgcHJlZGljdGlvbnMgYmFzZWQgb24gZGF0YS4gVGhleSB3b3JrIGJ5IGJyZWFraW5nIGRvd24gYSBkYXRhc2V0IGludG8gc21hbGxlciBzdWJzZXRzIHRocm91Z2ggInNwbGl0cywiIHJlc2VtYmxpbmcgYSB0cmVlIHdpdGggYnJhbmNoZXMuIEVhY2ggYnJhbmNoIHJlcHJlc2VudHMgYSBwb3NzaWJsZSBkZWNpc2lvbiBvciBvdXRjb21lLCBsZWFkaW5nIHRvIGEgZmluYWwgcHJlZGljdGlvbiBvciBjbGFzc2lmaWNhdGlvbi4KClRoZSBtYWluIGFkdmFudGFnZXMgb2YgZGVjaXNpb24gdHJlZXMgaW5jbHVkZSB0aGVpciBzaW1wbGljaXR5IGFuZCBpbnRlcnByZXRhYmlsaXR5LCBhcyB0aGV5IGFyZSBlYXN5IHRvIHZpc3VhbGl6ZSBhbmQgdW5kZXJzdGFuZC4gQW5vdGhlciBpbXBvcnRhbnQgYWR2YW50YWdlIGlzIHRoYXQgdGhleSBhcmUgYWJsZSB0byBsZWFybiB3aXRoIGRhdGEgd2l0aCBOL0EgdmFsdWVzLCBzb21ldGhpbmcgYWx0ZXJuYXRpdmVzIHN1Y2ggYXMgTG9naXN0aWMgUmVncmVzc2lvbi4gQ29uc2lkZXJpbmcgdGhlIGxpbWl0YXRpb24gd2UgaGF2ZSB3aXRoIG91ciBkYXRhIHNpemUgYW5kIG51bWJlciBvZiBOL0EgdmFsdWVzLCB0aGlzIGFuIGltcG9ydGFudCBhZHZhbnRhZ2UuIEhvd2V2ZXIsIHRoZXJlIGFyZSBzb21lIGRvd25zaWRlcyBzdWNoIGFzIGEgdGVuZGVuY3kgdG8gb3ZlcmZpdCB0aGUgZGF0YSwgd2hpY2ggaXMgc29tZXRoaW5nIHdlIG5lZWQgdG8gYmUgY2FyZWZ1bCBhYm91dC4KCiFbW0RlY2lzaW9uIFRyZWUgRXhhbXBsZSAoXXsudW5kZXJsaW5lfVtTb3VyY2VdKGh0dHBzOi8vZGF0YS1mbGFpci50cmFpbmluZy9ibG9ncy9yLWRlY2lzaW9uLXRyZWVzLykpXShEZWNUcmVlRWcucG5nKQoKIyMgUHJlLXByb2Nlc3NpbmcgdGhlIGRhdGEKCkZvciB0aGlzIGRhdGEsIHdlIHdpbGwgYmUgZGVhbGluZyB3aXRoIHRoZSBTdGF0dXMgdmFyaWFibGUuIFRoZSBTdGF0dXMgdmFyaWFibGUgd2FzIGRpdmlkZWQgaW50byAzIGNsYXNzZXM6CgotICAgQ2xhc3MgMCAoRCk6IFRoZSBwYXRpZW50IGRpZG4ndCBzdXJ2aXZlIGJ5IHRoZSBlbmQgb2YgdGhlIG9ic2VydmF0aW9uCi0gICBDbGFzcyAxIChDKTogVGhlIHBhdGllbnQgaXMgY2Vuc29yZWQsIG1lYW5pbmcgdGhhdCB0aGUgb2JzZXJ2YXRpb24gcGVyaW9kIGVuZGVkIHdpdGhvdXQgdGhlIGRlYXRoIGJlaW5nIHJlY29yZGVkCi0gICBDbGFzcyAyIChDTCk6IFNpbWlsYXIgdG8gQ2xhc3MgMSwgdGhlIHBhdGllbnQgaXMgY2Vuc29yZWQgZHVlIHRvIGxpdmVyIHRyYW5zcGxhbnRhdGlvbgoKVGh1cywgd2UgY2FuIGdyb3VwIENsYXNzIDEgYW5kIDIuCgpgYGB7cn0KI0NvbWJpbmUgQyBhbmQgQ0wgc3RhdHVzIGludG8gb25lIHZhcmlhYmxlIGFuZCBiaW5hcml6ZQpjaXJyaG9zaXNUcmVlRGF0YSA8LSBjaXJyaG9zaXMKY2lycmhvc2lzVHJlZURhdGEkU3RhdHVzIDwtIGlmZWxzZShjaXJyaG9zaXNUcmVlRGF0YSRTdGF0dXMgPT0gIkMiIHwgY2lycmhvc2lzVHJlZURhdGEkU3RhdHVzID09ICJDTCIsIDEsIDApCmBgYAoKIyMgTWV0aG9kb2xvZ3kKCk91ciBvYmplY3RpdmUgaXMgdG8gY3JlYXRlIGEgY2xhc3NpZmllciBjYXBhYmxlIG9mIHByZWRpY3RpbmcgYSBwYXRpZW50J3Mgb3V0Y29tZS4gVG8gYWNoaWV2ZSB0aGlzLCB3ZSB3aWxsIGJlIHRlc3Rpbmcgb3VyIGRhdGEgd2l0aCB0aGUgY2FydCBhbGdvcml0aG0uIFRvIGVuc3VyZSBtb2RlbCB2YWxpZGF0aW9uLCB3ZSdsbCBiZSB1c2luZyBhIDgwJSB0cmFpbmluZyBhbmQgMjAlIHRlc3RpbmcgZGF0YSBkaXZpc2lvbi4gSSB3aWxsIGFsc28gYmUgc3RyYXRpZnlpbmcgdGhlIGRhdGEgYmFzZWQgb24gdGhlIHN0YXR1cy4KClRvIGd1YXJhbnRlZSBjb25zaXN0ZW5jeSBhbmQgcmVwcm9kdWNpYmlsaXR5IGluIG91ciByZXN1bHRzLCB3ZSBoYXZlIGZpeGVkIHRoZSBzZWVkIGZvciBvdXIgODAvMjAgZGF0YSBzcGxpdCBhdCAzODAuIFdpdGggdGhlc2Ugc3RlcHMsIHdlIGFyZSBub3cgd2VsbC1wb3NpdGlvbmVkIHRvIGZpbmFsaXplIG91ciB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhIHNldHMuCgpUaGUgcHJlZGljdG9ycyBpbiB0aGVzZSBtb2RlbHMgd2lsbCBiZSBndWlkZWQgYnkgdGhlIHJlc3VsdHMgZnJvbSB0aGUgRURBLiBXZSB3aWxsIGFsc28gdXNlIGEgdmFyaWV0eSBvZiB0b29scyB0byB1bmRlcnN0YW5kIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlLgoKYGBge3J9CiMgV3JhbmdsZSB0aGUgR3JhZHVhdGUgZGF0YSB0byBzZXQgdXAgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YXNldHMKbW9kZWxDaXJyaG9zaXMgPC0gY2lycmhvc2lzVHJlZURhdGEgJT4lCiAgI2Ryb3BfbmEoKSAlPiUKICBtdXRhdGUoCiAgICB0ZW1wSUQgPSByb3dfbnVtYmVyKCksCiAgICAuYmVmb3JlID0gU3RhdHVzCiAgKQoKIyMgU2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eSBhbmQgc2xpY2UgLS0tLQpzZXQuc2VlZCgzODApCnRyYWluaW5nRGF0YSA8LSBtb2RlbENpcnJob3NpcyAlPiUKICBncm91cF9ieShTdGF0dXMpICU+JQogIHNsaWNlX3NhbXBsZShwcm9wID0gMC44KQoKdGVzdGluZ0RhdGEgPC0gbW9kZWxDaXJyaG9zaXMgJT4lCiAgZmlsdGVyKCEodGVtcElEICVpbiUgdHJhaW5pbmdEYXRhJHRlbXBJRCkpCmBgYAoKIyMjIFN0ZXAgMTogR3Jvd2luZyB0aGUgdHJlZQoKVGhlcmUgYXJlIHVzZWZ1bCBwYWNrYWdlcyB0byBidWlsZCBkZWNpc2lvbiB0cmVlcyBpbiBSOiB0aGUge3RyZWV9IGFuZCB0aGUge3JwYXJ0fSAocmVjdXJzaXZlIHBhcnRpdGlvbmluZykgcGFja2FnZXMuCgpJbiB0aGlzIHJlcG9ydCwgd2UgaGF2ZSBkZWNpZGVkIHRvIHVzZSB7cnBhcnR9IGJlY2F1c2UgaXQgcHJvdmlkZXMgbW9yZSBmbGV4aWJpbGl0eSBmb3Igc3Vycm9nYXRlIHNwbGl0cyBhbmQgdGhlIHRyZWVzIGFyZSBhIGJpdCBlYXNpZXIgdG8gbWFrZSBhdHRyYWN0aXZlIGxvb2tpbmcuCgpgYGB7cn0KIyBHcm93IEdyYWR1YXRlIHRyZWUgdmlhIHJwYXJ0IHBhY2thZ2UKbGlicmFyeShycGFydCkKclBhcnRTdGF0dXMgPC0gcnBhcnQoCiAgZm9ybXVsYSA9IFN0YXR1cyB+IERydWcgKyBBZ2UgKyBTZXggKyBBc2NpdGVzICsgSGVwYXRvbWVnYWx5ICsgU3BpZGVycyArIEVkZW1hICsgQmlsaXJ1YmluICsgQ2hvbGVzdGVyb2wgKyBBbGJ1bWluICsgQ29wcGVyICsgQWxrX1Bob3MgKyBTR09UICsgUGxhdGVsZXRzICsgUHJvdGhyb21iaW4gKyBTdGFnZSArIFRyeWdsaWNlcmlkZXMsIAogIGRhdGEgPSB0cmFpbmluZ0RhdGEsCiAgbWV0aG9kID0gImNsYXNzIiwKICBwYXJtcyA9IGxpc3Qoc3BsaXQgPSAiaW5mb3JtYXRpb24iKQogICMgV2UgZGlkIG5vdCBuZWVkIHRvIHVzZSB0aGUgY29udHJvbCBwYXJhbWV0ZXJzCikKCmBgYAoKIyMjIFBhcnQgMjogVmlzdWFsaXppbmcgdGhlIHRyZWUKCldpdGggdGhlIHRyZWUgZ3Jvd24sIHdlIGNhbiBub3cgdmlzdWFsaXplIGl0IGZvciBhbiBlYXN5IHVuZGVyc3RhbmRpbmcgb2YgaXRzIGZ1bmN0aW9uaW5nLiBUaGlzIGlzIGFuIGltcG9ydGFudCBhZHZhbnRhZ2UgZm9yIENBUlQgb3ZlciBsb2dpc3RpYyByZWdyZXNzaW9uLgoKVGhlIGZvbGxvd2luZyBpcyBhIGJhc2ljIGRpYWdyYW0gZm9yIHRoZSB0cmVlIHRoYXQgd2FzIGp1c3QgZ3Jvd24uCgpgYGB7cn0KIyBEaXNwbGF5IHJwYXJ0LnBsb3QgLS0tLQogbGlicmFyeShycGFydC5wbG90KQpycGFydC5wbG90KAogIHggPSByUGFydFN0YXR1cywKICB0eXBlID0gMiwKICBleHRyYSA9IDEwMQopCmBgYAoKVG8gZ2FpbiBhIGZ1cnRoZXIgdW5kZXJzdGFuZGluZyBvZiB0aGUgZGF0YSwgd2UgY2FuIHBsb3QgYSB0cmVlIHlpZWxkaW5nIENvbGxlY3Rpb24gTm9kZSBzdHlsZSB0cmVlcy4gVGhpcyBjYW4gaGVscCB1cyB1bmRlcnN0YW5kIGhvdyB0aGUgZGF0YSBpcyBzcGxpdC4KCmBgYHtyfQojIFVzaW5nIHRoZSByYXR0bGUgcGFja2FnZSB0byB2aXN1YWxpemUgdGhlIHRyZWUgLS0tLQpsaWJyYXJ5KHJhdHRsZSkKCmZhbmN5UnBhcnRQbG90KAogIG1vZGVsID0gclBhcnRTdGF0dXMsCiAgbWFpbiA9IE5VTEwsCiAgc3ViID0gTlVMTAopCmBgYAoKVGhlIHRyZWUgc2hvd3MgdXMgdGhlIHNwbGl0cyB0aGF0IHdlcmUgZG9uZSBvbiBBZ2UsIEJpbGlydWJpbiBhbmQgUHJvdGhyb21iaW4uIEludGVyZXN0aW5nbHksIFN0YWdlIGRpZCBub3QgY29udHJpYnV0ZSBpbiB0aGUgdHJlZS4KCiMjIyBQYXJ0IDM6IFBydW5pbmcgdGhlIHRyZWUKClBydW5pbmcgcmVkdWNlcyB0aGUgc2l6ZSBvZiBkZWNpc2lvbiB0cmVlcyBieSByZW1vdmluZyBwYXJ0cyBvZiB0aGUgdHJlZSB0aGF0IGRvIG5vdCBwcm92aWRlIHBvd2VyIHRvIGNsYXNzaWZ5IGluc3RhbmNlcy4gVGhlIGZpcnN0IHN0ZXAgb2YgcHJ1bmluZyBhIHRyZWUgaXMgdW5kZXJzdGFuZGluZyB0aGUgY29tcGxleGl0eSBwYXJhbWV0ZXIgdXNlZC4gVGhlIGNvbXBsZXhpdHkgcGFyYW1ldGVyIChjcCkgaW4gcnBhcnQgaXMgdGhlIG1pbmltdW0gaW1wcm92ZW1lbnQgaW4gdGhlIG1vZGVsIG5lZWRlZCBhdCBlYWNoIG5vZGUuIFRoaXMgaXMgdXNlZCB3aGVuIGJ1aWxkaW5nIHRoZSB0cmVlLiBXZSBjYW4gc2VlIHRoZSByZXN1bHRzIGJhc2VkIG9uIHRoZSBjcm9zcyB2YWxpZGF0aW9uIGZyb20gdGhlIHRhYmxlIGJlbG93LgoKYGBge3J9CmludmlzaWJsZShjYXB0dXJlLm91dHB1dCh7Y3BUYWJsZSA8LSBwcmludGNwKHJQYXJ0U3RhdHVzKX0pKQoKbGlicmFyeShrYWJsZUV4dHJhKQoKa2FibGUoCiAgeCA9IGNwVGFibGUsCiAgY29sLm5hbWVzID0gYygiQ1AiLCAiTnVtLiBvZiBzcGxpdHMiLCAiUmVsLiBFcnJvciIsCiAgICAgICAgICAgICAgICAiTWVhbiBFcnJvciIsICJTdGQuIERldmlhdGlvbiBvZiBFcnJvciIpLAogIGRpZ2l0cyA9IDMsCiAgYm9va3RhYnMgPSBUUlVFLAogIGFsaWduID0gImMiLAogIHRhYmxlLmF0dHIgPSAnZGF0YS1xdWFydG8tZGlzYWJsZS1wcm9jZXNzaW5nPSJ0cnVlIicKKQpgYGAKClRoaXMgY2FuIGFsc28gYmUgdmlzdWFsaXplZCBpbiBhIGdyYXBoIHRvIGdhaW4gYSBiZXR0ZXIgdW5kZXJzdGFuZGluZyBvZiB0aGUgZGF0YS4gVGhlIGdyYXBoIGJlbG93IHNob3dzIHRoZSBjb25uZWN0aW9uIGJldHdlZW4gdGhlIGNwLCBzaXplIG9mIHRyZWUgYW5kIHRoZSB4LXZhbCByZWxhdGl2ZSBlcnJvci4KCmBgYHtyfQpwbG90Y3AoCiAgeCA9IHJQYXJ0U3RhdHVzLAogIG1pbmxpbmUgPSBUUlVFLAogIHVwcGVyID0gInNpemUiCikKYGBgCgpGcm9tIHRoZSBncmFwaCB3ZSBjYW4gc2VlIHRoYXQgYSBjcCBvZiAwLjAzOSBpcyBpZGVhbCBhcyBpdCBpcyB1bmRlciB0aGUgaG9yaXpvbnRhbCAoZG90dGVkKSByZWZlcmVuY2UgbGluZS4gV2UgY2FuIHBydW5lIHRoZSB0cmVlIHdpdGggdGhpcyBDUCB2YWx1ZS4KCmBgYHtyfQojIFBydW5lIHRoZSBycGFydCBHcmFkdWF0ZSBUcmVlIC0tLS0KclBhcnRTdGF0dXMyIDwtIHBydW5lKAogIHRyZWUgPSByUGFydFN0YXR1cywKICBjcCA9IDAuMDI5CikKYGBgCgpXZSBjYW4gcGxvdCB0aGUgcHJ1bmVkIHRyZWUKCmBgYHtyfQpmYW5jeVJwYXJ0UGxvdCgKICBtb2RlbCA9IHJQYXJ0U3RhdHVzMiwKICBtYWluID0gTlVMTCwKICBzdWIgPSBOVUxMCikKYGBgCgpXZSBjYSBzZWUgdGhlIHBydW5lZCB0cmVlIGhhcyBjdXQgb3V0IHNvbWUgbGVhZiBub2Rlcy4gVGhpcyB3b3VsZCBoZWxwIGluIGF2b2lkaW5nIG92ZXJmaXR0aW5nIHRoZSBtb2RlbC4KCiMjIyBQYXJ0IDQ6IFJlc3VsdHMKCk5vdywgd2UgY2FuIGV2YWx1YXRlIHRoZSByZXN1bHRzIG9mIHRoZSB0cmVlIG9uIHRoZSB0ZXN0aW5nIGRhdGEgZnJvbSB0aGUgaW5pdGlhbCA4MC0yMCBzcGxpdC4gQXMgaXMgdHJ1ZSB3aGVuZXZlciB3ZSB1c2UgdmFsaWRhdGlvbiBhcHByb2FjaGVzLCB3ZSBuZWVkIHRvIHRlc3Qgb3V0IG91ciBtb2RlbCBvbiB0aGUgdGVzdGluZyBkYXRhIHNldC4gVGhpcyB3aWxsIGdpdmUgdXMgYSBtb3JlIGFjY3VyYXRlIHVuZGVyc3RhbmRpbmcgb2YgaG93IHdlbGwgdGhlIG1vZGVsIGZpdHMgdGhlIGNvbnRleHQgd2UncmUgc2Vla2luZyB0byBidWlsZCBvdXIgdW5kZXJzdGFuZGluZyBvZi4KCkFuIGltcG9ydGFudCBwYXJ0IG9mIG91ciByZXN1bHRzIGlzIHVuZGVyc3RhbmRpbmcgdGhlIHJvbGUgb2YgcHJlZGljdGlvbiBhbmQgaW5mZXJlbmNlLiBJbiBhIGJyb2FkIHNlbnNlLCBwcmVkaWN0aW9uIHJlZmVycyB0byB0aGUgcHJvY2VzcyBvZiBtYWtpbmcgZm9yZWNhc3RzIGFib3V0IGZ1dHVyZSBldmVudHMgb3IgdW5rbm93biB2YWx1ZXMgYmFzZWQgb24gYSBtb2RlbCB3aGlsZSBpbmZlcmVuY2UgZ2VuZXJhbGx5IHJlZmVycyB0byB0aGUgcHJvY2VzcyBvZiBkcmF3aW5nIGNvbmNsdXNpb25zIGZyb20gZGF0YS4gRm9yIHRoZSBiYXNpYyB0cmVlLCBJIHdpbGwgYmUgbWFpbmx5IGZvY3VzaW5nIG9uIHByZWRpY3Rpb24gYXNwZWN0cyBvZiB0aGUgcmVzdWx0cy4gSG93ZXZlciwgbGF0ZXIgaW4gdGhlIHJlcG9ydCwgd2Ugd2lsbCBhbHNvIGJlIGV4cGxvcmluZyBpbmZlcmVuY2UgZmluZGluZ3MuCgpgYGB7cn0KcHJlZF9TdGF0dXNScGFydDIgPC0gcHJlZGljdCgKICBvYmplY3QgPSByUGFydFN0YXR1czIsCiAgbmV3ZGF0YSA9IHRlc3RpbmdEYXRhLAogIHR5cGUgPSAicHJvYiIKKQoKIyBEYXRhIFdyYW5nbGluZyB0aGUgcHJlZGljdGlvbnMgLS0tLQpTdGF0dXNQcmVkaWN0aW9uIDwtIGRhdGEuZnJhbWUoCiAgcnBhcnQyX25vbl9kZWF0aCA9IHByZWRfU3RhdHVzUnBhcnQyWywgMV0sCiAgcnBhcnQyX2RlYXRoID0gcHJlZF9TdGF0dXNScGFydDJbLCAyXQopICU+JQogIG11dGF0ZSgKICAgIHJwYXJ0Ml9wcmVkID0gaWZlbHNlKAogICAgICB0ZXN0ID0gcnBhcnQyX2RlYXRoID4gcnBhcnQyX25vbl9kZWF0aCwKICAgICAgeWVzID0gMSwKICAgICAgbm8gPSAwCiAgICApCiAgKQoKIyMgU2V0IHByZWRpY3Rpb25zIGFzIGZhY3RvcnMKU3RhdHVzUHJlZGljdGlvbiRycGFydDJfcHJlZCA8LSBhcy5mYWN0b3IoU3RhdHVzUHJlZGljdGlvbiRycGFydDJfcHJlZCkKCiMgTWVyZ2Ugc3VwZXJ2aXNpb24gY29sdW1uIGludG8gcHJlZGljdGlvbnMgZGF0YSBmcmFtZSAtLS0tClN0YXR1c1ByZWRpY3Rpb24gPC0gY2JpbmQoCiAgdGVtcElEID0gdGVzdGluZ0RhdGEkdGVtcElELAogIFN0YXR1cyA9IHRlc3RpbmdEYXRhJFN0YXR1cywKICBTdGF0dXNQcmVkaWN0aW9uCikKYGBgCgpXZSBjYW4gZXZhbHVhdGUgdGhlIHJlc3VsdHMgb2YgdGhpcyB0aHJvdWdoIGEgY29uZnVzaW9uIG1hdHJpeC4KCmBgYHtyfQpTdGF0dXNQcmVkaWN0aW9uJFN0YXR1cyA8LSBmYWN0b3IoU3RhdHVzUHJlZGljdGlvbiRTdGF0dXMpCgpsaWJyYXJ5KHlhcmRzdGljaykKCiMgQnVpbGQgY29uZnVzaW9uIG1hdHJpeCBmb3Igc2Vjb25kIHRyZWUgbW9kZWwKY29uZl9tYXRyaXggPC0gY29uZl9tYXQoCiAgZGF0YSA9IFN0YXR1c1ByZWRpY3Rpb24sCiAgdHJ1dGggPSBTdGF0dXMsCiAgZXN0aW1hdGUgPSBycGFydDJfcHJlZAopJHRhYmxlCgprYWJsZSgKICBjb25mX21hdHJpeCwKICBjb2wubmFtZXMgPSBjKCJQcmVkaWN0aW9uL1N1cGVydmlzaW9uIiwgIjAiLCAiMSIpLAogIGRpZ2l0cyA9IDMsCiAgYm9va3RhYnMgPSBUUlVFLAogIGNhcHRpb24gPSAiTW9kZWwgMTogQ29uZnVzaW9uIE1hdHJpeCAoMD1EZWNlYXNlZCwgMT1DZW5zb3JlZCkiLAogIGFsaWduID0gImMiCikgJT4lCmthYmxlX3N0eWxpbmcobGF0ZXhfb3B0aW9ucyA9ICJIT0xEX3Bvc2l0aW9uIikKCgphY2N1cmFjeSA8LSBhY2N1cmFjeShTdGF0dXNQcmVkaWN0aW9uLCBTdGF0dXMsIHJwYXJ0Ml9wcmVkKQpzcGVjaWZpY2l0eSA8LSBzcGVjaWZpY2l0eShTdGF0dXNQcmVkaWN0aW9uLCBTdGF0dXMsIHJwYXJ0Ml9wcmVkKQpzZW5zaXRpdml0eSA8LSBzZW5zaXRpdml0eShTdGF0dXNQcmVkaWN0aW9uLCBTdGF0dXMsIHJwYXJ0Ml9wcmVkKQpgYGAKCmBgYHtyfQojIEJ1aWxkIGEgZGF0YSBmcmFtZSB3aXRoIG1vZGVsIG1ldHJpY3MgLS0tLQpTdGF0dXNQcmVkcyA8LSBTdGF0dXNQcmVkaWN0aW9uICU+JQogIGRwbHlyOjpzZWxlY3QodGVtcElELCBTdGF0dXMsIGNvbnRhaW5zKCJfcHJlZCIpKSAlPiUKICBwaXZvdF9sb25nZXIoCiAgICBjb2xzID0gIWModGVtcElELCBTdGF0dXMpLAogICAgbmFtZXNfdG8gPSAibW9kZWwiLAogICAgdmFsdWVzX3RvID0gInByZWRpY3Rpb24iCiAgKQoKYWNjdXJhY3kgPC0gU3RhdHVzUHJlZHMgJT4lCiAgZ3JvdXBfYnkobW9kZWwpICU+JQogIGFjY3VyYWN5KAogICAgdHJ1dGggPSBTdGF0dXMsCiAgICBlc3RpbWF0ZSA9IHByZWRpY3Rpb24KICApCgpzZW5zaXRpdml0eSA8LSBTdGF0dXNQcmVkcyAlPiUKICBncm91cF9ieShtb2RlbCkgJT4lCiAgc2Vuc2l0aXZpdHkoCiAgICB0cnV0aCA9IFN0YXR1cywKICAgIGVzdGltYXRlID0gcHJlZGljdGlvbiwKICAgIGV2ZW50X2xldmVsID0gInNlY29uZCIKICApCgpzcGVjaWZpY2l0eSA8LSBTdGF0dXNQcmVkcyAlPiUKICBncm91cF9ieShtb2RlbCkgJT4lCiAgc3BlY2lmaWNpdHkoCiAgICB0cnV0aCA9IFN0YXR1cywKICAgIGVzdGltYXRlID0gcHJlZGljdGlvbiwKICAgIGV2ZW50X2xldmVsID0gInNlY29uZCIKICApCgptb2RlbE1ldHJpY3MgPC0gYmluZF9yb3dzKAogIGFjY3VyYWN5LAogIHNlbnNpdGl2aXR5LAogIHNwZWNpZmljaXR5CikKYGBgCgpXaXRoIHRoaXMsIHdlIGNhbiBhbHNvIGNhbGN1bGF0ZSB0aGUgbW9kZWwncyBtZXRyaWNzIG9uIHRoZSB0ZXN0IGRhdGEuCgpgYGB7cn0KIyBNYWtlIGEgbmljZSBsb29raW5nIHRhYmxlIG9mIG1vZGVsIG1ldHJpY3MgLS0tLQptb2RlbE1ldHJpY3MgJT4lCiAgZHBseXI6OnNlbGVjdChtb2RlbCwgLm1ldHJpYywgLmVzdGltYXRlKSAlPiUKICBwaXZvdF93aWRlcigKICAgIGlkX2NvbHMgPSBtb2RlbCwKICAgIG5hbWVzX2Zyb20gPSAubWV0cmljLAogICAgdmFsdWVzX2Zyb20gPSAuZXN0aW1hdGUKICApICU+JQogIGthYmxlKAogICAgZGlnaXRzID0gMywKICAgIGJvb2t0YWJzID0gVFJVRSwKICAgIGFsaWduID0gImMiLAogICAgdGFibGUuYXR0ciA9ICdkYXRhLXF1YXJ0by1kaXNhYmxlLXByb2Nlc3Npbmc9InRydWUiJwogICkKYGBgCgpBcyB5b3UgY2FuIHNlZSB0aGUgbW9kZWwgc2hvd3MgZ29vZCBhY2N1cmFjeSB3aXRoIDcyLjklLiBIb3dldmVyLCB0aGUgc3BlY2lmaWNpdHkgaXMgYW4gaXNzdWUgd2l0aCA1Ny42JS4gTm93IGxldCB1cyBjb21wYXJlIHRoaXMgd2l0aCBhIGRpZmZlcmVudCB0eXBlIG9mIG1vZGVsOiBsb2dpc3RpYyByZWdyZXNzaW9uLgoKLS0tCnRpdGxlOiAiUmVncmVzc2lvbi1iYXNlZCIKYXV0aG9yOiAiTWloaXIgS3Vsa2FybmksIE5pdGhpa2EgTWVub24iCmRhdGU6ICIyMDIzLTEyLTEyIgpvdXRwdXQ6IGh0bWxfZG9jdW1lbnQKLS0tCgojIFBhcnQgSUk6IEV4cGxvcmluZyB0aGUgcHJlZGljdG9ycyBmb3IgdGhlIHBhdGllbnQncyBzdGF0dXMgdXNpbmcgbG9naXN0aWMgcmVncmVzc2lvbgoKTmV4dCwgbGV0IHVzIGV4cGxvcmUgb3VyIHF1ZXN0aW9uIHdpdGggYSBkaWZmZXJlbnQgdHlwZSBvZiBtb2RlbCBhbmQgY29tcGFyZSB0aGUgcmVzdWx0cyB3aXRoIG91ciB0cmVlLiBUbyBkbyB0aGlzLCB3ZSB3aWxsIGJlIHVzaW5nIChiaW5hcnkpIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwuCgojIyBJbnRyb2R1Y3Rpb24KCkxvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgYSBzdGF0aXN0aWNhbCBtZXRob2QgdXNlZCBmb3IgYmluYXJ5IGNsYXNzaWZpY2F0aW9uLCB3aGljaCBwcmVkaWN0cyB0aGUgcHJvYmFiaWxpdHkgb2YgYW4gb3V0Y29tZSB0aGF0IGNhbiBiZSBlaXRoZXIgdHJ1ZSBvciBmYWxzZS4gVGhpcyBpcyBkb25lIGJ5IHVuZGVyc3RhbmRpbmcgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGEgZGVwZW5kZW50IGJpbmFyeSB2YXJpYWJsZSBhbmQgb25lIG9yIG1vcmUgaW5kZXBlbmRlbnQgdmFyaWFibGVzLiBMb2dpc3RpYyByZWdyZXNzaW9uIGlzIGVhc3kgdG8gaW1wbGVtZW50IGFuZCBpbnRlcnByZXQuIEhvd2V2ZXIgdGhlcmUgYXJlIGFsc28gc29tZSBkcmF3YmFja3MsIHRoZSBtb2RlbCBhc3N1bWVzIGEgbGluZWFyIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMgYW5kIHRoZSBsb2cgb2RkcyBvZiB0aGUgZGVwZW5kZW50IHZhcmlhYmxlLCB3aGljaCBtYXkgbm90IGFsd2F5cyBob2xkIHRydWUgaW4gY29tcGxleCByZWFsLXdvcmxkIHNjZW5hcmlvcy4gRnVydGhlcm1vcmUsIHVubGlrZSB0aGUgZGVjaXNpb24gdHJlZSwgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzIGNhbm5vdCBpZ25vcmUgTi9BIHZhbHVlcy4KCiFbW0xvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwgKFtTb3VyY2VdKGh0dHBzOi8vd3d3LnNwaWNld29ya3MuY29tL3RlY2gvYXJ0aWZpY2lhbC1pbnRlbGxpZ2VuY2UvYXJ0aWNsZXMvd2hhdC1pcy1sb2dpc3RpYy1yZWdyZXNzaW9uLykpXXsudW5kZXJsaW5lfV0oNDYtNC5wbmcpCgojIyBNZXRob2RvbG9neQoKU2ltaWxhciB0byB0aGUgdHJlZSwgd2Ugd2lsbCBzdGFydCBieSBzcGxpdHRpbmcgdGhlIGRhdGEgc2V0IGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cy4gTm90ZSB0aGF0IHRoZSBkYXRhIG5vdyBvbmx5IGNvbnRhaW5zIGluc3RhbmNlcyB3aGVyZSB0aGUgcGF0aWVudCB3YXMgZGVjZWFzZWQuIFRoZSB0cmFpbmluZyBzZXQgd2lsbCBiZSB1c2VkIHRvIHRyYWluIG91ciBtb2RlbCwgd2hpbGUgdGhlIHRlc3Rpbmcgc2V0IHdpbGwgaGVscCBldmFsdWF0ZSBpdHMgcGVyZm9ybWFuY2UuIFdlJ2xsIHVzZSA4MCUgb2YgdGhlIGRhdGEgZm9yIHRyYWluaW5nIGFuZCB0aGUgcmVtYWluaW5nIDIwJSBmb3IgdGVzdGluZy4gVG8gYWxsb3cgcmVwcm9kdWNpYmxlIGNvZGUsIHdlIGhhdmUgZml4ZWQgdGhlIHNlZWQgYXQgMzgwLgoKV2l0aCB0aGlzLCB3ZSB3aWxsIGJ1aWxkIHR3byBjYW5kaWRhdGUgbW9kZWxzOgoKLSAgIFRoZSBmaXJzdCBtb2RlbCB3aWxsIHRlc3QgdGhlIGNsYXNzaWZpY2F0aW9uIGJhc2VkIG9uIGp1c3QgdGhlIFNHT1QKCi0gICBUaGUgc2Vjb25kIG1vZGVsIHdpbGwgdXNlIGEgc3RlcCB3aXNlIGZ1bmN0aW9uIHVzaW5nIHZhcmlvdXMgcHJlZGljdG9ycyB0byBzZWUgdGhlIGJlc3QgcGVyZm9ybWFuY2UuCgpBbm90aGVyIGltcG9ydGFudCBjb25zaWRlcmF0aW9uIGlzIHRoZSBhcHBsaWNhdGlvbiBvZiBwcmVkaWN0aW9uIChlc3RpbWF0aW5nIGFuIG91dGNvbWUgYmFzZWQgb24gaW5wdXQgdmFyaWFibGVzKSBhbmQgaW5mZXJlbmNlICh1bmRlcnN0YW5kaW5nIHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gdmFyaWFibGVzKS4gV2Ugd2lsbCBldmFsdWF0ZSB0aGUgaW5mZXJlbmNlIHRocm91Z2ggdGhlIGNvZWZmaWNpZW50IGFuYWx5c2lzIGFuZCBwcmVkaWN0aW9uIHRocm91Z2ggcm9jIGN1cnZlcyBhbmQgY29uZnVzaW9uIG1hdHJpeC4gSXQgaXMgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCB0aGVzZSBtZXRyaWNzIHdpbGwgY29tcGxlbWVudCBlYWNoIG90aGVyIGluIG91ciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBkYXRhLiBIb3dldmVyLCB0aGUgbWFpbiBmb2N1cyBvZiB0aGlzIGFuYWx5c2lzIHdpbGwgYmUgcHJlZGljdGlvbiBhbmQgd2Ugd2lsbCB3b3JrIHdpdGggc2V2ZXJhbCBtZXRyaWNzIHRvIGV2YWx1YXRlIGl0LgoKV2Ugd2lsbCB1c2UgYSB2YXJpZXR5IG9mIHRvb2xzIHRvIHVuZGVyc3RhbmQgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UuCgpgYGB7cn0KY2lycmhvc2lzUmVncmVzc2lvbiA8LSBjaXJyaG9zaXMKY2lycmhvc2lzUmVncmVzc2lvbiRTdGF0dXMgPC0gaWZlbHNlKGNpcnJob3Npc1JlZ3Jlc3Npb24kU3RhdHVzID09ICJDIiB8IGNpcnJob3Npc1JlZ3Jlc3Npb24kU3RhdHVzID09ICJDTCIsIDEsIDApCmBgYAoKYGBge3J9CiNtb2RlbCBkYXRhCkxSbW9kZWxEYXRhIDwtIGNpcnJob3Npc1JlZ3Jlc3Npb24gJT4lCiAgZHJvcF9uYSgpICU+JQogIG11dGF0ZSgKICAgIHRlbXBJRCA9IHJvd19udW1iZXIoKSwKICAgIC5iZWZvcmUgPSBTdGF0dXMKICApCgojIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkgYW5kIHNsaWNlCnNldC5zZWVkKDM4MCkKdHJhaW5pbmdEYXRhIDwtIExSbW9kZWxEYXRhICU+JQogIGdyb3VwX2J5KFN0YXR1cykgJT4lICAjIGdyb3VwX2J5KCkgZnVuY3Rpb24gZW5zdXJlcyB0aGF0IHRoZSBkYXRhCiAgc2xpY2Vfc2FtcGxlKHByb3AgPSAwLjgwKQoKdGVzdGluZ0RhdGEgPC0gTFJtb2RlbERhdGEgJT4lCiAgZmlsdGVyKCEodGVtcElEICVpbiUgdHJhaW5pbmdEYXRhJHRlbXBJRCkpCgp0cmFpbmluZ1Jlc3VsdHMgPC0gdHJhaW5pbmdEYXRhCmBgYAoKYGBge3J9CiMgRm9ybSBDYW5kaWRhdGUgTW9kZWwgMQptb2RlbDEgPC0gZ2xtKAogIGZvcm11bGEgPSBTdGF0dXMgfiBTR09ULAogIGRhdGEgPSB0cmFpbmluZ0RhdGEsCiAgZmFtaWx5ID0gYmlub21pYWwKKQpgYGAKClN0ZXB3aXNlIHJlc3VsdHM6CgpgYGB7ciBtb2RlbHN9CiMgTG93ZXIgYm91bmQgKEludGVyY2VwdCBvbmx5KQpsb3dlciA8LSBnbG0oCiAgZm9ybXVsYSA9IFN0YXR1cyB+IDEsCiAgZGF0YSA9IHRyYWluaW5nRGF0YSwKICBmYW1pbHkgPSBiaW5vbWlhbAopCgojIFVwcGVyIGJvdW5kIAp1cHBlciA8LSBnbG0oCiAgZm9ybXVsYSA9IFN0YXR1cyB+IERydWcgKyBBZ2UgKyBTZXggKyBBc2NpdGVzICsgSGVwYXRvbWVnYWx5ICsgU3BpZGVycyArIEVkZW1hICsgQmlsaXJ1YmluICsgQ2hvbGVzdGVyb2wgKyBBbGJ1bWluICsgQ29wcGVyICsgQWxrX1Bob3MgKyBTR09UICsgUGxhdGVsZXRzICsgUHJvdGhyb21iaW4gKyBTdGFnZSwKICBkYXRhID0gdHJhaW5pbmdEYXRhLAogIGZhbWlseSA9IGJpbm9taWFsCikKCgojIFN0ZXB3aXNlIHNlYXJjaAptb2RlbDIgPC0gc3RlcCgKICBvYmplY3QgPSBsb3dlciwKICBzY29wZSA9IGxpc3QoCiAgICBsb3dlciA9IGxvd2VyLAogICAgdXBwZXIgPSB1cHBlcgogICksCiAgZGF0YSA9IHRyYWluaW5nRGF0YSwKICBkaXJlY3Rpb24gPSAiYm90aCIsCiAgayA9IDIKKQpgYGAKCiMgUmVzdWx0cwoKSW5pdGlhbGx5LCB3ZSdsbCBkZWx2ZSBpbnRvIHRoZSB0d28gcHJlbGltaW5hcnkgbW9kZWxzIGluZGVwZW5kZW50bHkgdG8gdW5kZXJzdGFuZCB3aGVyZSB0aGV5IHN0YW5kLiBGb2xsb3dpbmcgdGhhdCwgd2UnbGwgYmUgZGVwbG95aW5nIHRoZSBiZXN0IGNhbmRpZGF0ZSBtb2RlbCBvbiBvdXIgdGVzdCBkYXRhLiBSZWdhcmRpbmcgY29uZnVzaW9uIG1hdHJpY2VzLCB3ZSdsbCBlbXBsb3kgYSBiYXNpYyBydWxlOiBpZiB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIGEgdGhlIHBhdGllbnQncyBzdGF0dXMgYmVpbmcgImRlY2Vhc2VkIiBleGNlZWRzIDAuNSwgd2UnbGwgY2F0ZWdvcml6ZSB0aGVtIGFzIGRlY2Vhc2VkIChuYcOvdmUgcnVsZSkuCgojIyBNb2RlbCAxCgpgYGB7cn0KIyBNb2RlbCAxIENvZWZmaWNpZW50IFRhYmxlCmFzLmRhdGEuZnJhbWUoc3VtbWFyeShtb2RlbDEpJGNvZWZmaWNpZW50cykgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJYIikgJT4lCiAgcmVuYW1lKGNvZWZmaWNpZW50ID0gRXN0aW1hdGUpICU+JSAKICBtdXRhdGUoCiAgICBwcm9iX29kZHMgPSBjYXNlX3doZW4oCiAgICAgIGNvZWZmaWNpZW50ID09ICIoSW50ZXJjZXB0KSIgfiBleHAoY29lZmZpY2llbnQpLygxICsgZXhwKGNvZWZmaWNpZW50KSksCiAgICAgIC5kZWZhdWx0ID0gZXhwKGNvZWZmaWNpZW50KQogICAgKSwKICAgIC5hZnRlciA9IGNvZWZmaWNpZW50CiAgKSAlPiUKICBtdXRhdGUoCiAgICBgUHIoPnx6fClgID0gaWZlbHNlKAogICAgICB0ZXN0ID0gYFByKD58enwpYCA8IDAuMDAxLAogICAgICB5ZXMgPSBwYXN0ZSgiPCAwLjAwMSIpLAogICAgICBubyA9IGBQcig+fHp8KWAKICAgICksCiAgICBYID0gY2FzZV93aGVuKAogICAgICBYID09ICIoSW50ZXJjZXB0KSIgfiAiSW50ZXJjZXB0IiwKICAgICAgZ3JlcGwoeCA9IFgsIHBhdHRlcm4gPSAiU0dPVCIpIH4gIlNHT1QiCiAgICApCiAgKSAlPiUKICBrYWJsZSgpCgpgYGAKClRoaXMgdGFibGUgc2hvd3MgdXMgdGhlIHJlc3VsdHMgb2Ygb3VyIGZpcnN0IG1vZGVsLiBXZSBjYW4gc2VlIHRoYXQsIGhvbGRpbmcgb3RoZXIgdmFyaWFibGVzIGNvbnN0YW50LCBhIG9uZS11bml0IGluY3JlYXNlIGluIFNHT1QgaXMgYXNzb2NpYXRlZCB3aXRoIGEgZGVjcmVhc2UgaW4gdGhlIGxvZy1vZGRzIG9mIHRoZSByZXNwb25zZSB2YXJpYWJsZSBieSAwLjAwNzY0NDYuIEZ1cnRoZXJtb3JlLCB0aGUgb2Rkcy1yYXRpbyBpbmRpY2F0ZXMgdGhhdCBmb3IgZWFjaCBvbmUtdW5pdCBpbmNyZWFzZSBpbiBTR09ULCB0aGUgb2RkcyBvZiB0aGUgZXZlbnQgb2NjdXJyaW5nIGRlY3JlYXNlIGJ5IGFib3V0IDAuNzYlLgoKV2UgY2FuIGFsc28gcGxvdCB0aGUgY29uZnVzaW9uIG1hdHJpeCBmb3IgdGhpcyBtb2RlbDoKCmBgYHtyfQpsaWJyYXJ5KGphbml0b3IpCiMgQnVpbGRpbmcgY29uZmlkZW5jZSBpbnRlcnZhbHMgZm9yIE1vZGVsIDEgY29lZmZpY2llbnRzCm1vZGVsMUNJIDwtIGNvbmZpbnQoCiAgb2JqZWN0ID0gbW9kZWwxLAogIHBhcm0gPSAiU0dPVCIsCiAgbGV2ZWwgPSAwLjkKKQoKdHJhaW5pbmdSZXN1bHRzIDwtIHRyYWluaW5nRGF0YSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKG1vZGVsMVByZWQgPSBwcmVkaWN0KG9iamVjdCA9IG1vZGVsMSwgbmV3ZGF0YSA9IC4sIHR5cGUgPSAicmVzcG9uc2UiKSkKCiMgQXBwbHkgbmHDr3ZlIHJ1bGUgLS0tLQp0cmFpbmluZ1Jlc3VsdHMgPC0gdHJhaW5pbmdSZXN1bHRzICU+JQogIG11dGF0ZSgKICAgIG1vZGVsMUNsYXNzID0gY2FzZV93aGVuKAogICAgICBtb2RlbDFQcmVkID4gMC41IH4gIkNlbnNvcmVkIiwKICAgICAgLmRlZmF1bHQgPSAiRGVjZWFzZWQiCiAgICApCiAgKQoKI0NvbmZ1c2lvbiBNYXRyaXggZm9yIE1vZGVsIDEKdHJhaW5pbmdSZXN1bHRzICU+JQogIG11dGF0ZShQYXRpZW50X3N0YXR1cyA9IGlmZWxzZShTdGF0dXMgPT0gMSwgIkNlbnNvcmVkIiwgIkRlY2Vhc2VkIikpICU+JQogIHRhYnlsKHZhcjEgPSBtb2RlbDFDbGFzcywgdmFyMiA9IFBhdGllbnRfc3RhdHVzKSAlPiUKICBhZG9ybl90aXRsZSgKICAgIHBsYWNlbWVudCA9ICJjb21iaW5lZCIsCiAgICByb3dfbmFtZSA9ICJQcmVkaWN0ZWQiLAogICAgY29sX25hbWUgPSAiQWN0dWFsIgogICkgJT4lCiAga2FibGUoCiAgICBib29rdGFicyA9IFRSVUUsCiAgICBhbGlnbiA9ICJjIiwKICAgIGNhcHRpb24gPSAiTW9kZWwgMSBDb25mdXNpb24gTWF0cml4IgogICklPiVrYWJsZV9zdHlsaW5nKGxhdGV4X29wdGlvbnMgPSAiSE9MRF9wb3NpdGlvbiIpCgpgYGAKCldlIGNhbiBzZWUgdGhhdCB0aGlzIG1vZGVsIHRlbmRzIHRvIG92ZXIgcHJlZGljdCBjZW5zb3JlZCB2YWx1ZXMuIFRoaXMgc2hvd3MgdGhlIG5lZWQgb2YgYnJpbmdpbmcgaW4gbW9yZSBmYWN0b3JzLiBOZXh0IGxldCB1cyBsb29rIGF0IG91ciBNb2RlbCAyLCB3aGljaCBoYXMgbXVsdGlwbGUgZmFjdG9ycyBhcyBkaXNjdXNzZWQgZWFybGllci4KCmBgYHtyfQojQ29lZmYgZm9yIG1vZGVsIDIKYXMuZGF0YS5mcmFtZShzdW1tYXJ5KG1vZGVsMikkY29lZmZpY2llbnRzKSAlPiUKICByZW5hbWUoY29lZmZpY2llbnQgPSBFc3RpbWF0ZSkgJT4lIAogIG11dGF0ZSgKICAgIHByb2Jfb2RkcyA9IGNhc2Vfd2hlbigKICAgICAgY29lZmZpY2llbnQgPT0gIihJbnRlcmNlcHQpIiB+IGV4cChjb2VmZmljaWVudCkvKDEgKyBleHAoY29lZmZpY2llbnQpKSwKICAgICAgVFJVRSB+IGV4cChjb2VmZmljaWVudCkKICAgICksCiAgICAuYWZ0ZXIgPSBjb2VmZmljaWVudAogICkgJT4lCiAga2FibGUoKQoKYGBgCgpUaGlzIGlzIHRoZSByb2xlIG9mIGluZmVyZW5jZSBpbiBldmFsdWF0aW5nIG91ciBtb2RlbC4gVGhlIG1vc3Qgbm90YWJsZSBwcmVkaWN0b3JzIGFyZSBCaWxpcnViaW4sIEFnZSwgQWxrX1Bob3MsIGFuZCBQcm90aHJvbWJpbiwgZWFjaCBzaG93aW5nIGEgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCByZWxhdGlvbnNoaXAgKHAgXDwgMC4wNSkgd2l0aCB0aGUgZGVwZW5kZW50IHZhcmlhYmxlLiBUaGUgSW50ZXJjZXB0IGFuZCBFZGVtYVkgaGF2ZSBleHRyZW1lbHkgc2lnbmlmaWNhbnQgcC12YWx1ZXMsIGJ1dCB0aGUgcHJhY3RpY2FsIHNpZ25pZmljYW5jZSBvZiBFZGVtYVkgaXMgcXVlc3Rpb25hYmxlIGR1ZSB0byBpdHMgbGFyZ2Ugc3RhbmRhcmQgZXJyb3IuIE90aGVyIHZhcmlhYmxlcyBsaWtlIFNwaWRlcnMsIFNHT1QsIERydWdQbGFjZWJvLCBhbmQgQ29wcGVyLCB3aGlsZSBjb250cmlidXRpbmcgdG8gdGhlIG1vZGVsLCBkbyBub3QgcmVhY2ggY29udmVudGlvbmFsIGxldmVscyBvZiBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2UgKHAgXDwgMC4wNSkuCgpgYGB7cn0KI2RvIHRoZSBUdWtleS1BbnNjb21iZSBwbG90CmdncGxvdCgKICBkYXRhID0gZGF0YS5mcmFtZSgKICAgIHJlc2lkdWFscyA9IHJlc2lkdWFscyhtb2RlbDIsIHR5cGUgPSAicGVhcnNvbiIpLAogICAgZml0dGVkID0gZml0dGVkKG1vZGVsMikKICApLAogIG1hcHBpbmcgPSBhZXMoeCA9IGZpdHRlZCwgeSA9IHJlc2lkdWFscykKKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aCgKICAgIGZvcm11bGEgPSB5IH4geCwKICAgIG1ldGhvZCA9IHN0YXRzOjpsb2VzcywKICAgIG1ldGhvZC5hcmdzID0gbGlzdChkZWdyZWUgPSAxKSwKICAgIHNlID0gRkFMU0UsCiAgICBsaW5ld2lkdGggPSAwLjUKICApICsKICB0aGVtZV9idygpICsKICBsYWJzKAogICAgeCA9ICJGaXR0ZWQiLAogICAgeSA9ICJQZWFyc29uIFJlc2lkdWFscyIKICApCmBgYAoKVGhpcyBmaWd1cmUgc2hvd3MgdXMgdGhlIFR1a2V5LUFuc2NvbWJlIHBsb3QgdXNpbmcgUGVhcnNvbiByZXNpZHVhbHMgZm9yIE1vZGVsIDIuIEluIGFuIGlkZWFsIGZpdCwgdGhlIHJlc2lkdWFscyBzaG91bGQgYmUgZXZlbmx5IGRpc3RyaWJ1dGVkIGFib3V0IHplcm8gd2l0aCBjb25zdGFudCBtZWFuIGFuZCB2YXJpYW5jZS4gVGhlIHNoYXBlIG9mIHRoZSBsaW5lIHN1Z2dlc3RzIHRoYXQgdGhlIG1vZGVsIGlzIG5vdCBjYXB0dXJpbmcgc29tZXVuZGVyIGx5aW5nIHN0cnVjdHVyZSBpbiB0aGUgZGF0YWluIGV4dHJlbWUgY2FzZXMuCgpgYGB7cn0KI3Bsb3QgdGhlIGd2aWYKYXMuZGF0YS5mcmFtZShjYXI6OnZpZihtb2RlbDIpKSAlPiUKICBrYWJsZSgKICAgIGRpZ2l0cyA9IDMsCiAgICBhbGlnbiA9ICJsY2NjYyIsCiAgICBib29rdGFiID0gVFJVRSwKICAgIGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIiksCiAgICB0YWJsZS5hdHRyID0gJ2RhdGEtcXVhcnRvLWRpc2FibGUtcHJvY2Vzc2luZz0idHJ1ZSInLAogICAgbGFiZWwgPSAiR1ZJRiBhbmFsc3lpcyIKICApCgpgYGAKClRoZSBWYXJpYW5jZSBJbmZsYXRpb24gRmFjdG9yIChWSUYpIHZhbHVlcyBmb3IgdGhlIHZhcmlhYmxlcyBpbiB0aGUgbW9kZWwgKEJpbGlydWJpbiwgQWdlLCBBbGtfUGhvcywgUHJvdGhyb21iaW4sIFNwaWRlcnMpIGFyZSBhbGwgY2xvc2UgdG8gMSwgaW5kaWNhdGluZyBtaW5pbWFsIG11bHRpY29sbGluZWFyaXR5LiBUaGlzIG1lYW5zIHRoYXQgdGhlc2UgcHJlZGljdG9ycyBhcmUgcmVsYXRpdmVseSBpbmRlcGVuZGVudCBvZiBlYWNoIG90aGVyLCBlbmhhbmNpbmcgdGhlIHJlbGlhYmlsaXR5IG9mIHRoZSBtb2RlbC4KCmBgYHtyfQojU3RvcmUgdGhlIHByZWRpY3RlZCBhbmQgYWN0dWFsIHZhbHVlcyBmb3IgTW9kZWwgMgp0cmFpbmluZ1Jlc3VsdHMkbW9kZWwyUHJlZCA8LSBwcmVkaWN0KG1vZGVsMiwgdHlwZSA9ICJyZXNwb25zZSIpCnRyYWluaW5nUmVzdWx0cyRtb2RlbDJDbGFzcyA8LSBpZmVsc2UodHJhaW5pbmdSZXN1bHRzJG1vZGVsMlByZWQgPiAwLjUsICJDZW5zb3JlZCIsICJEZWNlYXNlZCIpCnRyYWluaW5nUmVzdWx0cyRBY3R1YWwgPC0gaWZlbHNlKHRyYWluaW5nRGF0YSRTdGF0dXMgPT0gMSwgIkNlbnNvcmVkIiwgIkRlY2Vhc2VkIikKCiMgQ3JlYXRlIGNvbmZ1c2lvbiBtYXRyaXggdXNpbmcgdGFibGUKY29uZnVzaW9uTWF0cml4UmVncmVzc2lvbiA8LSB0YWJsZShQcmVkaWN0ZWQgPSB0cmFpbmluZ1Jlc3VsdHMkbW9kZWwyQ2xhc3MsIEFjdHVhbCA9IHRyYWluaW5nUmVzdWx0cyRBY3R1YWwpCgprYWJsZShjb25mdXNpb25NYXRyaXhSZWdyZXNzaW9uLCBjYXB0aW9uID0gIkNvbmZ1c2lvbiBtYXRyaXggZm9yIE1vZGVsIDIiKSAlPiUKICBrYWJsZV9jbGFzc2ljKGxhdGV4X29wdGlvbnMgPSAiSE9MRF9wb3NpdGlvbiIpCgpgYGAKCkZyb20gdGhpcyBjb25mdXNpb24gbWF0cml4IHdlIGNhbiBzZWUgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiB0aGUgVHJ1ZSBQb3NpdGl2ZSwgVHJ1ZSBOZWdhdGl2ZSwgRmFsc2UgUG9zaXRpdmUgYW5kIEZhbHNlIE5lZ2F0aXZlIHZhbHVlcy4gRnJvbSB0aGlzIHdlIGNhbiBjYWxjdWxhdGU6CgotICAgKipBY2N1cmFjeSoqOiBBcHByb3hpbWF0ZWx5IDgxLjM2JQoKLSAgICoqUmVjYWxsKio6IEFwcHJveGltYXRlbHkgNzkuMDElCgotICAgKipQcmVjaXNpb24qKjogQXBwcm94aW1hdGVseSA3Mi43MyUKCi0gICAqKkYxIFNjb3JlKio6IEFwcHJveGltYXRlbHkgNzUuNzQlCgpMYXN0bHksIGxldCB1cyBsb29rIGF0IHRoZSBzZXBhcmF0aW9uIHBsb3RzIGZvciBlYWNoIG9mIHRoZSBtb2RlbHMuCgpgYGB7cn0KbGlicmFyeShwUk9DKQpsaWJyYXJ5KHNlcGFyYXRpb25wbG90KQojIEZpdCBST0MgQ3VydmVzIGZvciBsYXRlcgojIyBNb2RlbCAxCm1vZGVsMVJPQyA8LSByb2MoCiAgZm9ybXVsYSA9IFN0YXR1cyB+IG1vZGVsMVByZWQsCiAgZGF0YSA9IHRyYWluaW5nUmVzdWx0cwopCm1vZGVsMVJPQ19kZiA8LSBkYXRhLmZyYW1lKAogIHRocmVzaG9sZCA9IG1vZGVsMVJPQyR0aHJlc2hvbGRzLAogIHNlbnNpdGl2aXR5ID0gbW9kZWwxUk9DJHNlbnNpdGl2aXRpZXMsCiAgc3BlY2lmaWNpdHkgPSBtb2RlbDFST0Mkc3BlY2lmaWNpdGllcywKICBtb2RlbCA9ICJNb2RlbCAxIgopCiMjIE1vZGVsIDIKbW9kZWwyUk9DIDwtIHJvYygKICBmb3JtdWxhID0gU3RhdHVzIH4gbW9kZWwyUHJlZCwKICBkYXRhID0gdHJhaW5pbmdSZXN1bHRzCikKbW9kZWwyUk9DX2RmIDwtIGRhdGEuZnJhbWUoCiAgdGhyZXNob2xkID0gbW9kZWwyUk9DJHRocmVzaG9sZHMsCiAgc2Vuc2l0aXZpdHkgPSBtb2RlbDJST0Mkc2Vuc2l0aXZpdGllcywKICBzcGVjaWZpY2l0eSA9IG1vZGVsMlJPQyRzcGVjaWZpY2l0aWVzLAogIG1vZGVsID0gIk1vZGVsIDIiCikKYGBgCgpgYGB7cn0KIyBDb252ZXJ0ICdBY3R1YWwnIGNvbHVtbiB0byBudW1lcmljIDAvMQp0cmFpbmluZ1Jlc3VsdHMgPC0gdHJhaW5pbmdSZXN1bHRzICU+JQogIG11dGF0ZSgKICAgIGFjdHVhbE51bSA9IGlmX2Vsc2UoQWN0dWFsID09ICJEZWNlYXNlZCIsIDAsIDEpCiAgKQoKCiNTZXBlYXRpb24gUGxvdApwYXIobWFyID0gYyg0LDAsMCwwKSkKc2VwYXJhdGlvbnBsb3QoCiAgcHJlZCA9IHRyYWluaW5nUmVzdWx0cyRtb2RlbDFQcmVkLCAKICBhY3R1YWwgPSB0cmFpbmluZ1Jlc3VsdHMkYWN0dWFsTnVtLCAKICB0eXBlID0gInJlY3QiLAogIGxpbmUgPSBUUlVFLCAKICBsd2QyID0gMiwKICBzaG93LmV4cGVjdGVkID0gVFJVRSwgCiAgbmV3cGxvdCA9IEZBTFNFLAogIGhlYWRpbmcgPSAiTW9kZWwgMSIKKQoKYGBgCgpgYGB7cn0KI1NlcGVhdGlvbiBQbG90CnBhcihtYXIgPSBjKDQsMCwwLDApKQpzZXBhcmF0aW9ucGxvdCgKICBwcmVkID0gdHJhaW5pbmdSZXN1bHRzJG1vZGVsMlByZWQsIAogIGFjdHVhbCA9IHRyYWluaW5nUmVzdWx0cyRhY3R1YWxOdW0sIAogIHR5cGUgPSAicmVjdCIsCiAgbGluZSA9IFRSVUUsIAogIGx3ZDIgPSAyLAogIHNob3cuZXhwZWN0ZWQgPSBUUlVFLCAKICBuZXdwbG90ID0gRkFMU0UsCiAgaGVhZGluZyA9ICJNb2RlbCAyIgopCmBgYAoKVGhlIHNlcGFyYXRpb24gcGxvdCBhc3Nlc3NlcyB0aGUgdGhlIGZpdCBvZiB0aGUgbW9kZWwgYnkgcHJvdmlkaW5nIHRoZSBtb2RlbCdzIGFiaWxpdHkgdG8gcHJlZGljdCBvY2N1cnJlbmNlcyB3aXRoIGEgaGlnaCBwcm9iYWJpbGl0eSBhbmQgbm9uLW9jY3VycmVuY2VzIHdpdGggbG93IHByb2JhYmlsaXR5LiBUaGUgc2VwYXJhdGlvbiBwbG90IGFib3ZlIHNlcGFyYXRpb24gcGxvdCBzdWdnZXN0cyB0aGF0IE1vZGVsIDIgaGFzIGEgcmVhc29uYWJseSBnb29kIHBlcmZvcm1hbmNlIGluIHByZWRpY3RpbmcgdGhlIHBhdGllbnQncyBzdGF0dXMsIGVzcGVjaWFsbHkgZm9yIHRoZSBvYnNlcnZhdGlvbnMgb24gdGhlIGxlZnQtbW9zdCBzaWRlIG9mIHRoZSBwbG90IGNvbXBhcmVkIHRvIE1vZGVsIDEgd2l0aCB0aGUgdHJhaW5pbmcgZGF0YS4gV2Ugd2lsbCBsYXRlciBjb21wYXJlIHRoaXMgZ3JhcGggd2l0aCB0aGUgdGVzdGluZyBkYXRhLgoKTGFzdGx5LCBsZXQgdXMgbG9vayBhdCB0aGUgUk9DIGN1cnZlcyBmb3IgYm90aCB0aGUgbW9kZWxzLgoKYGBge3J9CiMjIE1lcmdlIGludG8gZXhpc3RpbmcgZGF0YSBmcmFtZQpyb2NEYXRhIDwtIHJiaW5kKG1vZGVsMVJPQ19kZiwgbW9kZWwyUk9DX2RmKQoKIyMgQVVDIERhdGEKYXVjRGF0YSA8LSBkYXRhLmZyYW1lKAogIG1vZGVsID0gYygiTW9kZWwgMSIsICJNb2RlbCAyIiksCiAgYXVjID0gYyhtb2RlbDFST0MkYXVjLCBtb2RlbDJST0MkYXVjKQopCmBgYAoKYGBge3J9CiNST0MgcGxvdApnZ3Bsb3QoCiAgZGF0YSA9IHJvY0RhdGEsCiAgbWFwcGluZyA9IGFlcyh4ID0gMSAtIHNwZWNpZmljaXR5LCB5ID0gc2Vuc2l0aXZpdHksIGNvbG9yID0gbW9kZWwpCikgKwogIGdlb21fcGF0aCgpICsKICBnZW9tX2FibGluZSgKICAgIHNsb3BlID0gMSwKICAgIGludGVyY2VwdCA9IDAsCiAgICBsaW5ldHlwZSA9ICJkb3R0ZWQiCiAgKSArCiAgZ2VvbV90ZXh0KAogIGluaGVyaXQuYWVzID0gRkFMU0UsCiAgZGF0YSA9IGF1Y0RhdGEsCiAgbWFwcGluZyA9IGFlcyhsYWJlbCA9IHBhc3RlKG1vZGVsLCAiQVVDOiBcbiIsIHJvdW5kKGF1YywgMykpKSwKICB4ID0gYygwLjI1LCAwLjE1KSwKICB5ID0gYygwLjQsIDEuMDUpCikKYGBgCgpGcm9tIHRoZSBncmFwaHMgd2UgY2FuIGludGVycHJldCB0aGF0OgoKTW9kZWwgMToKCi0gICBJdHMgUk9DIGN1cnZlIGlzIGFib3ZlIHRoZSBsaW5lIG9mIG5vIGRpc2NyaW1pbmF0aW9uLCBpbmRpY2F0aW5nIHRoYXQgdGhlIG1vZGVsIGhhcyBzb21lIHByZWRpY3RpdmUgY2FwYWJpbGl0aWVzCgotICAgVGhlIEFVQyBpcyAwLjYzOCwgd2hpY2ggaXMgYmV0dGVyIHRoYW4gcmFuZG9tIGd1ZXNzaW5nIGJ1dCBzdWdnZXN0cyB0aGVyZSdzIHJvb20gZm9yIGltcHJvdmVtZW50LgoKTW9kZWwgMjoKCi0gICBUaGUgUk9DIGN1cnZlIGZvciBNb2RlbCAyIGlzIHNpZ25pZmljYW50bHkgYWJvdmUgdGhhdCBvZiBNb2RlbCAxLCBhbmQgbXVjaCBjbG9zZXIgdG8gdGhlIHRvcC1sZWZ0IGNvcm5lciwgaW5kaWNhdGluZyBiZXR0ZXIgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZS4KCi0gICBUaGUgQVVDIGlzIDAuODk5LCB3aGljaCBzdWdnZXN0cyBhIGdvb2QgY2xhc3NpZmljYXRpb24gcGVyZm9ybWFuY2UsIGFuZCBpdCdzIG5vdGFibHkgYmV0dGVyIHRoYW4gTW9kZWwgMS4KCi0gICBJdHMgYWJpbGl0eSB0byBkaXNjcmltaW5hdGUgYmV0d2VlbiBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgY2xhc3NlcyBpcyBzdXBlcmlvciB0byB0aGF0IG9mIE1vZGVsIDEuCgpMYXN0bHksIGxldCB1cyBwbG90IG91ciBpbmZsdWVuY2UgcGxvdC4KCmBgYHtyfQojIEluZmx1ZW5jZSBQbG90IGZvciBNb2RlbCAyCmlkQ2FzZXMgPC0gY2FyOjppbmZsdWVuY2VQbG90KG1vZGVsMikKYGBgCgpUaGUgaW5mbHVlbmNlIHBsb3Qgc2hvd3Mgc2V2ZXJhbCBkYXRhIHBvaW50cyB3aXRoIGhpZ2ggbGV2ZXJhZ2UgYW5kIGxhcmdlIHJlc2lkdWFscywgaW5kaWNhdGluZyBwb3RlbnRpYWwgb3V0bGllcnMuIFNvbWUgb2JzZXJ2YXRpb25zLCBub3RhYmx5IHRob3NlIGxhYmVsZWQgbGlrZSAiMTQ5LCIgaGF2ZSBzaWduaWZpY2FudCBpbmZsdWVuY2Ugb24gdGhlIHJlZ3Jlc3Npb24gbW9kZWwgZHVlIHRvIHRoZWlyIENvb2sncyBEIHZhbHVlcy4KCiMgVGVzdGluZyBvdXIgbW9kZWwKClVzaW5nIG91ciBmaW5hbCBtb2RlbCwgd2Ugbm93IHR1cm4gdG8gc2VlIGhvdyB3ZWxsIHRoaXMgY2xhc3NpZmllciBkb2VzIG9uIG91ciB0ZXN0aW5nIGRhdGEuIFJlY2FsbCB0aGF0IHdlIGluaXRpYWxseSBzZXQgdGhlIHRlc3QgZGF0YSBkdXJpbmcgdGhlIHRyYWluIHRlc3Qgc3BsaXQuCgpUaGUgY29uZnVzaW9uIG1hdHJpeCBiZWxvdyBzaG93cyB0aGUgcGVyZm9ybWFuY2Ugb2Ygb3VyIG1vZGVsIHVzaW5nIHRoZSBuYcOvdmUgZGVjaXNpb24gcnVsZS4KCmBgYHtyfQojIFNldCB1cCB0ZXN0aW5nIGRhdGEgcmVzdWx0cwp0ZXN0aW5nRGF0YSA8LSB0ZXN0aW5nRGF0YSAlPiUKICBtdXRhdGUoCiAgICBncmFkTnVtID0gY2FzZV93aGVuKAogICAgICBTdGF0dXMgPT0gMCB+IDAsCiAgICAgIFN0YXR1cyA9PSAxIH4gMQogICAgKSwKICAgIC5hZnRlciA9IFN0YXR1cwogICkKdGVzdGluZ0RhdGEkcHJlZGljdCA8LSBwcmVkaWN0KAogIG9iamVjdCA9IG1vZGVsMiwKICBuZXdkYXRhID0gdGVzdGluZ0RhdGEsCiAgdHlwZSA9ICJyZXNwb25zZSIKKQp0ZXN0aW5nRGF0YSA8LSB0ZXN0aW5nRGF0YSAlPiUKICBtdXRhdGUoCiAgICBtb2RlbDJDbGFzcyA9IGNhc2Vfd2hlbigKICAgICAgcHJlZGljdCA+IDAuNSB+ICJDZW5zb3JlZCIsCiAgICAgIC5kZWZhdWx0ID0gIkRlY2Vhc2VkIgogICAgKQogICkKYGBgCgpgYGB7cn0KdGVzdGluZ0RhdGEkU3RhdHVzIDwtIGlmZWxzZSh0ZXN0aW5nRGF0YSRTdGF0dXMgPT0gMSwgIkNlbnNvcmVkIiwgIkRlY2Vhc2VkIikKCiMgQnVpbGQgQ29uZnVzaW9uIE1hdHJpeCBmb3IgVGVzdGluZyBEYXRhCnRlc3RpbmdEYXRhICU+JQogIHRhYnlsKHZhcjEgPSBtb2RlbDJDbGFzcywgdmFyMiA9IFN0YXR1cykgJT4lCiAgYWRvcm5fdGl0bGUoCiAgICBwbGFjZW1lbnQgPSAiY29tYmluZWQiLAogICAgcm93X25hbWUgPSAiUHJlZGljdGVkIiwKICAgIGNvbF9uYW1lID0gIkFjdHVhbCIKICApICU+JQogIGthYmxlKAogICAgY2FwdGlvbiA9ICJDb25mdXNpb24gTWF0cml4IGZvciBUZXN0IGRhdGEiCiAgKQpgYGAKCi0gICAqKkFjY3VyYWN5Kio6IEFwcHJveGltYXRlbHkgNzEuNDMlCgotICAgKipSZWNhbGwqKjogQXBwcm94aW1hdGVseSA3Ni45MiUKCi0gICAqKlByZWNpc2lvbioqOiBBcHByb3hpbWF0ZWx5IDQzLjQ4JQoKLSAgICoqRjEgU2NvcmUqKjogQXBwcm94aW1hdGVseSA1NS41NiUKClRoaXMgc2hvd3MgdGhhdCBvdXIgbW9kZWwgaXMgaGFzIHN0cnVnZ2xlZCB3aXRoIG92ZXJmaXR0aW5nLCB3aXRoIGFuIGVzcGVjaWFsbHkgbG93IHByZWNpc2lvbiBzY29yZS4gV2Ugd2lsbCBkaXNjdXNzIHRoaXMgaW4gdGhlIGNvbXBhcmlzb24gd2l0aCB0aGUgdHJlZSBtb2RlbCwgYnV0IHRoaXMgaXMgYW4gaW1wb3J0bmF0IGxpbWl0YXRpb24gb2Ygb3VyIGRhdGEgc2l6ZSBhcyB3ZSBkaXNjdXNzZWQgaW4gdGhlIGludHJvZHVjdGlvbi4gTGFzdGx5LCB3ZSBjYW4gcGxvdCB0aGUgc2VwYXJhdGlvbiBwbG90LiBXZSBjYW4gc2VlIHRoZSBzZXBhcmF0aW9uIGhhcyBpbmNyZWFzZWQgZHVlIHRvIHRoZSBvdmVyIGZpdHRpbmcgd2UgZGlzY3Vzc2VkLgoKYGBge3J9CiNTZXBlYXRpb24gUGxvdApwYXIobWFyID0gYyg0LDAsMCwwKSkKc2VwYXJhdGlvbnBsb3QoCiAgcHJlZCA9IHRlc3RpbmdEYXRhJHByZWRpY3QsIAogIGFjdHVhbCA9IHRlc3RpbmdEYXRhJGdyYWROdW0sIAogIHR5cGUgPSAicmVjdCIsCiAgbGluZSA9IFRSVUUsIAogIGx3ZDIgPSAyLAogIHNob3cuZXhwZWN0ZWQgPSBUUlVFLCAKICBuZXdwbG90ID0gRkFMU0UKKQoKYGBgCgojQ29tcGFyaXNvbiBiZXR3ZWVuIG1vZGVscwoKTm93IGxldCB1cyBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBvdXIgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3aXRoIGEgZGVjaXNpb24gdHJlZSBtb2RlbC4gV2Ugd2lsbCB1c2UgdGhlIHNhbWUgdHJhaW4gdGVzdCBzcGxpdCBhcyBiZWZvcmUuCgojIFBhcnQgSUlJOiBFeHBsb3JpbmcgdGhlIHN1Yi1jbHVzdGVycyBvZiB0aGUgY2lycmhvc2lzIGRhdGEgdXNpbmcgdW5zdXBlcnZpc2VkIGxlYXJuaW5nCgpJbiB0aGlzIHNlY3Rpb24sIHdlIHNvdWdodCB0byBpZiB0aGVyZSBhcmUgc3ViLWNvbGxlY3Rpb25zIG9mIHBhdGllbnRzLiBXZSBleHBlY3Qgc3ViLWNvbGxlY3Rpb25zIHRvIG1pbWljIG1lZGljYWwgZGVmaW5pdGlvbnMgb2YgdGhlIHN0YWdlcyBvZiBCaWxpYXJ5IENpcnJob3NpcyBidXQgYXJlIGN1cmlvdXMgdG8gc2VlIGlmIGNsdXN0ZXJpbmcgY2FuIHJldmVhbCBzb21lIG5ld2VyIG9ic2VydmF0aW9ucy4KCiMjIEludHJvZHVjdGlvbgoKQmlsaWFyeSBDaXJyaG9zaXMgcGF0aWVudHMgYXJlIHR5cGljYWxseSBjbHVzdGVyZWQgaW4gZm91ciBtYWluIGNsdXN0ZXJzIChbU291cmNlXShodHRwczovL3d3dy5oZWFsdGhsaW5lLmNvbS9oZWFsdGgvcHJpbWFyeS1iaWxpYXJ5LWNpcnJob3NpcyNzdGFnZXMpey51cml9KToKCi0gICBTdGFnZSAxOiBUaGVyZSdzIGluZmxhbW1hdGlvbiBhbmQgZGFtYWdlIHRvIHRoZSB3YWxscyBvZiBtZWRpdW0tc2l6ZWQgYmlsZSBkdWN0cy4KCi0gICBTdGFnZSAyOiBUaGVyZSdzIGJsb2NrYWdlIG9mIHRoZSBzbWFsbCBiaWxlIGR1Y3RzLgoKLSAgIFN0YWdlIDM6IFRoaXMgc3RhZ2UgbWFya3MgdGhlIGJlZ2lubmluZyBvZiBzY2FycmluZy4KCi0gICBTdGFnZSA0OiBDaXJyaG9zaXMgaGFzIGRldmVsb3BlZC4gVGhpcyBwZXJtYW5lbnQsIHNldmVyZSwgc2NhcnJpbmcgYW5kIGRhbWFnZSB0byB0aGUgbGl2ZXIuCgpJbiB0aGlzIHNlY3Rpb24gd2Ugd2lsbCBiZSB1c2luZyBjbHVzdGVyaW5nIG9uIG91ciBkYXRhLiBLLW1lYW5zIENsdXN0ZXJpbmcgaXMgYW4gdW5zdXBlcnZpc2VkIGxlYXJuaW5nIHRlY2huaXF1ZSB3aGVyZSBkYXRhIHBvaW50cyBhcmUgZ3JvdXBlZCBiYXNlZCBvbiB0aGVpciBzaW1pbGFyaXRpZXMuIEl0J3MgY29tbW9ubHkgdXNlZCB0byBpZGVudGlmeSBwYXR0ZXJucyBhbmQgc3RydWN0dXJlcyB3aXRoaW4gZGF0YXNldHMgd2l0aG91dCBwcmlvciBrbm93bGVkZ2Ugb2YgdGhlIGdyb3Vwcy4gVGhlIG1haW4gYWR2YW50YWdlIG9mIGNsdXN0ZXJpbmcgaXMgaXRzIGFiaWxpdHkgdG8gZGlzY292ZXIgaGlkZGVuIHBhdHRlcm5zIGluIGRhdGEuIEhvd2V2ZXIsIGEgc2lnbmlmaWNhbnQgZHJhd2JhY2sgaXMgdGhlIHN1YmplY3Rpdml0eSBpbiBkZWZpbmluZyB0aGUgJ3NpbWlsYXJpdHknIGNyaXRlcmlhLCB3aGljaCBjYW4gbGVhZCB0byB2YXJ5aW5nIHJlc3VsdHMgYW5kIGludGVycHJldGF0aW9ucy4KClshW0stbWVhbnMgY2x1c3RlcmluZyAoW1NvdXJjZV0oaHR0cHM6Ly9tZWRpdW0uZGF0YWRyaXZlbmludmVzdG9yLmNvbS9rLW1lYW5zLWNsdXN0ZXJpbmctNGE3MDBkNGE0NzIwKSldKDEqZnotcmpZUFBSbEdFTWRUSS1STGJEZy5wbmcpXShodHRwczovL21lZGl1bS5kYXRhZHJpdmVuaW52ZXN0b3IuY29tL2stbWVhbnMtY2x1c3RlcmluZy00YTcwMGQ0YTQ3MjApCgpBbiBpbXBvcnRhbnQgbW90aXZhdGlvbiBmb3IgdGhpcyBwYXJ0IG9mIHRoZSBzdHVkeSBpcyBleHBsb3JlIGhvdyBkb2VzICJTdGFnZSIgd2hpY2ggaXMgdHlwaWNhbGx5IGRlZmluZWQgYmFzZWQgb24gbWVkaWNhbCBwcm9mZXNzaW9uYWxzJyBvYnNlcnZhdGlvbnMgY29tcGFyZXMgdG8gdGhlIGRhdGEgdGhhdCB3ZSBoYXZlIGluIHRoaXMgZGF0YSBzZXQuCgojIyBQcmUtcHJvY2Vzc2luZyB0aGUgZGF0YQoKV2UgaGFkIHRvIHByZS1wcm9jZXNzIHRoZSBkYXRhIGZvciB0aGUgYmVzdCBwZXJmb3JtYW5jZS4gRmlyc3QsIHdlIHJlbW92ZWQgdGhlICdTdGFnZScgY29sdW1uIHRvIGF2b2lkIHVzaW5nIG91dGNvbWUtcmVsYXRlZCBmZWF0dXJlcyBpbiB0aGUgdW5zdXBlcnZpc2VkIGxlYXJuaW5nLiBUaGUgcm93cyB3aXRoIE4vQSB2YWx1ZXMgd2VyZSBhbHNvIHJlbW92ZWQuIExhc3RseSwgd2UgdHJhbnNmb3JtZWQgdmFyaW91cyBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW50byBudW1lcmljIGZvcm1hdHMsIHdoaWNoIGlzIG5lY2Vzc2FyeSBmb3IgY2x1c3RlcmluZyBhbGdvcml0aG1zLgoKYGBge3J9CiNkbyB0aGUgbWwsIHRha2UgYWJvdXQgb2JzZXJ2YXRpb25hbCBzdGFnZXMgdnMgbm93IHF1YW50aWZ5LCBsb29rIGludG8gdGhlIGRpZmZlcmVudCB2YXJpYWJsZXMgYW5kIHdoYXQgdGhleSBtZWFuLgpjaXJyaG9zaXNDbHVzdGVyIDwtIGNpcnJob3NpcwpjaXJyaG9zaXNDbHVzdGVyMiA8LSBuYS5vbWl0KGNpcnJob3NpcykKCmNpcnJob3Npc0NsdXN0ZXIgPC0gY2lycmhvc2lzQ2x1c3RlciAlPiUgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdCgtU3RhZ2UpICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApjaXJyaG9zaXNDbHVzdGVyIDwtIG5hLm9taXQoY2lycmhvc2lzQ2x1c3RlcikgI2Nhbm5vdCBoYXZlIE5BIHZhbHVlcyBpbiBjbHVzdGVyaW5nCgpjaXJyaG9zaXNDbHVzdGVyJEFnZV9ZZWFycyA8LSByb3VuZChjaXJyaG9zaXNDbHVzdGVyJEFnZV9ZZWFycykgI3JvdW5kIGFnZSB0byB3aG9sZSBudW1iZXJzCgoKI3Jlb2RlIHNleC4uLiByZWNvZGUgb3RoZXJzIGxhdGVyIGlmIG5lZWQgYmUgI2ZlbWFsZSBpcyAwCgojY2lycmhvc2lzQ2x1c3RlciAgPC0gY2lycmhvc2lzQ2x1c3RlciAlPiUKIyAgc2VsZWN0KCAtYyhTdGF0dXMsIERydWcsIEVkZW1hKSkgJT4lCiAgCiMgbXV0YXRlKFNleCA9IGlmZWxzZShTZXggPT0gJ0ZlbWFsZScsIDAsIDEpKQogICAgIAogICAgICAKICBjaXJyaG9zaXNDbHVzdGVyICA8LSBjaXJyaG9zaXNDbHVzdGVyICU+JQogICAgbXV0YXRlKFNleCA9IGlmZWxzZShTZXggPT0gJ0ZlbWFsZScsIDAsIDEpKSAlPiUKICAgICAgICAgIG11dGF0ZShUcmFuc3BsYW50ID0gaWZlbHNlKFN0YXR1cyA9PSAiQ0wiLCAxLCAwKSkgJT4lCiAgICAgICAgICAgICAgbXV0YXRlKFN0YXR1cyA9IGlmZWxzZShTdGF0dXMgJWluJSBjKCdDJywgJ0NMJyksIDAgLCAxKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgbXV0YXRlKERydWcgPSBpZmVsc2UoRHJ1ZyA9PSAiRC1wZW5pY2lsbGFtaW5lIiwgMCwgMSkpICU+JQogICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUoRWRlbWFEaXVyZWN0aWNzID0gaWZlbHNlKEVkZW1hICVpbiUgYygnUycsICdZJyksIDEsIDApKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKE5vRWRlbWFPUkQgPSBpZmVsc2UoRWRlbWEgPT0gJ04nICwgMSwgMCkpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKEVkZW1hQU5ERCA9IGlmZWxzZShFZGVtYSA9PSAiWSIsIDEsIDApKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKEVkZW1hT1JEID0gaWZlbHNlKEVkZW1hID09ICJTIiwgMSwgMCkpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KC1FZGVtYSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAKCiNEYXRhIGlzIGFscmVhZHkgZmFjdG9yZWQKICAgICAKI211dGF0ZShTZXggPSBpZmVsc2UoU2V4ID09ICdGZW1hbGUnLCAwLCAxKSkgJT4lCiAgCiAgICNtdXRhdGUoU3RhdHVzID0gaWZlbHNlKFN0YXR1cyAlaW4lIGMoJ0MnLCAnQ0wnKSwgMCAsIDEpKSAlPiUKICAgICAgICAgICMgICAgbXV0YXRlKERydWcgPSBpZmVsc2UoRHJ1ZyA9PSAiRC1wZW5pY2lsbGFtaW5lIiwgMCwgMSkpCgogIAojPz8/OgogIAojbWFrZSBjb2x1bW4geWVzIG5vIGVkZW1hICN0aGVuIHllcyBubyB1bmRlciB0cmVhdG1lbnQKCiNzYW1lIGZvciB0cmFuc3BsYW50IHN0dWZmCmBgYAoKYGBge3J9CmRpc3RDaXJyaG9zaXMgPC0gZGlzdCgKICB4ID0gY2lycmhvc2lzQ2x1c3RlciwKICBtZXRob2QgPSAiZXVjbGlkZWFuIgopCgpgYGAKCiMjIE1ldGhvZG9sb2d5CgpUbyBhcHBseSB0aGUgay1tZWFucyBhbGdvcml0aG0uIEEgbWl4ZWQgaGllcmFyY2hpY2FsIGFuZCBub24taGllcmFyY2hpY2FsIHdhcyBhcHBsaWVkLiBUaGlzIHdhcyBkb25lIHVzaW5nIHRoZSBoa21lYW5zbSBtb2R1bGUsIHdpdGggayA9IDQgYXMgb3VyIGluaXRpYWwgdmFsdWUuIEJhc2VkIG9uIHRoZSBkYXRhIHByb3BlcnRpZXMsIHRoZSBldWNsaWRlYW4gKEwyKSBkaXN0YW5jZSB3b3JrcyBiZXN0LgoKYGBge3J9CgpoeWJyaWRDaXJyaG9zaXMgPC0gaGttZWFucygKICB4ID0gY2lycmhvc2lzQ2x1c3RlciwKICBrID0gNCwKICBoYy5tZXRyaWMgPSAiZXVjbGlkZWFuIiwKICBoYy5tZXRob2QgPSAid2FyZC5EIiwKICBpdGVyLm1heCA9IDEwCikKCmBgYAoKIyMgUmVzdWx0cwoKV2Ugd2VyZSBhYmxlIHRvIHZpc3VhbGl6ZSB0aGUgcmVzdWx0cyBvZiB0aGUgay1tZWFucyBhbGdvcml0aG0uIFRoZSBmaXJzdCBzdGVwIHdhcyBtYWtpbmcgYSBjb2xvciBwYWxldHRlIGFuZCBwbG90dGluZyB0aGUgZGVuZHJvZ3JhbSB0cmVlLgoKYGBge3J9CgpTdGFnZXNQYWxldHRlIDwtIGMoIiNBQTMzNkEiLCAiIzc3MDczNyIsICIjNDBCNUFEIiwgIiMwMDlFNjAiLCAiIzlGRTJCRiIpCgpgYGAKCmBgYHtyfQojIyBNQUtFIEEgTkVXIFBBTExFVEUgVE8gVklTVUFMSVpFCiMgUGxvdCB0aGUgaW5pdGlhbCBkZW5kcm9ncmFtIGZvciBoeWJyaWQgYXBwcm9hY2ggLS0tLQpzZXQuc2VlZCgzODApCmhrbWVhbnNfdHJlZSgKICBoa21lYW5zID0gaHlicmlkQ2lycmhvc2lzLAogIHJlY3QuY29sID0gU3RhZ2VzUGFsZXR0ZSwKICBjZXggPSAwLjQsCiAgbWFpbiA9ICJJbml0aWFsIEhpZXJhcmNoaWNhbCBDbHVzdGVycyIKKQoKYGBgCgpBcywgeW91IGNhbiBzZWUsIHRoZSBtb2RlbCB3YXMgYWJsZSB0byBjcmVhdGUgY2xlYXIgY2x1c3RlcnMgZm9yIHRoZSBkYXRhLiBIb3dldmVyLCBpdCBpcyBpbXBvcnRhbnQgdG8gZmluZCB0aGUgYmVzdCB2YWx1ZSBvZiBrIHRvIGdldCB0aGUgb3B0aW1hbCBjbHVzdGVycy4gVG8gZG8gdGhpcyB3ZSBjYW4gdXNlIGEgc2NyZWUgcGxvdC4gSGVyZSwgd2VcJ3JlIGxvb2tpbmcgZm9yIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgdGhhdCBjb3JyZXNwb25kcyB0byB0aGUgXCJlbGJvd1wiLgoKYGBge3J9CiMgQ3JlYXRlIHNjcmVlIHBsb3QgZm9yIGNob29zaW5nIGsgLS0tLQpsaWJyYXJ5KGZhY3RvZXh0cmEpCnNldC5zZWVkKDM4MCkKZnZpel9uYmNsdXN0KAogIHggPSBjaXJyaG9zaXNDbHVzdGVyLAogIGRpc3MgPSBOVUxMLAogIEZVTmNsdXN0ZXIgPSBrbWVhbnMsCiAgbWV0aG9kID0gIndzcyIsCiAgay5tYXggPSAxMAopCgoKYGBgCgpGcm9tIHRoaXMsIHdlIGlkZW50aWZpZWQgdGhhdCA1IHdhcyB0aGUgaWRlYWwgdmFsdWUgb2Ygaywgd2hlcmUgdGhlIFRvdGFsIFdpdGhpbiBbQ2x1c3Rlcl0gU3VtcyBvZiBTcXVhcmVzIGJlZ2lucyBsZXZlbGluZyBvZmYuIExldCB1cyBjcmVhdGUgYSBuZXcgay1tZWFucyBtb2RlbCB3aXRoIGsgPSA1LiBXZSBjYW4gcGxvdCB0aGlzIHJlZmluZWQgbW9kZWwsIHdpdGggYSBmb3JtYXQgbW9yZSBlYXN5IHRvIHZpc3VhbGl6ZS4KCmBgYHtyfQoKaHlicmlkQ2lycmhvc2lzMiA8LSBoa21lYW5zKAogIHggPSBjaXJyaG9zaXNDbHVzdGVyLAogIGsgPSA1LAogIGhjLm1ldHJpYyA9ICJldWNsaWRlYW4iLAogIGhjLm1ldGhvZCA9ICJ3YXJkLkQiLAogIGl0ZXIubWF4ID0gMTAKKQoKYGBgCgpgYGB7cn0KIyBQbG90IHRoZSBmaW5hbCBkZW5kcm9ncmFtIGZvciBoeWJyaWQgYXBwcm9hY2ggLS0tLQojIGxpYnJhcnkoZmFjdG9leHRyYSkKZnZpel9kZW5kKAogIHggPSBoeWJyaWRDaXJyaG9zaXMyLAogIGNleCA9IDAuNCwKICBwYWxldHRlID0gU3RhZ2VzUGFsZXR0ZSwKICByZWN0ID0gRkFMU0UsCiAgaG9yaXogPSBUUlVFLAogIHJlcGVsID0gVFJVRSwKICBtYWluID0gIkZpbmFsIERlbmRyb2dyYW0iCikKCgoKYGBgCgpBcyB5b3UgY2FuIHNlZSwgdGhlIDUgY2x1c3RlcnMgd2VyZSBpZGVudGlmaWVkLCB3aGljaCBhcmUgY29sb3IgY29kZWQgaW4gb3VyIHVwZGF0ZWQgZGlhZ3JhbS4KCkxhc3RseSwgbGV0IHVzIHBsb3QgdGhlc2UgY2x1c3RlcnMuIEhlcmUgaXMgYSBwbG90IG9mIHRoZSBpbml0aWFsIG1vZGVsIHdoZXJlIGsgPSA0CgpgYGB7cn0KIyBQbG90IHRoZSBmaW5hbCBjbHVzdGVyaW5nIGZvciBoeWJyaWQgYXBwcm9hY2ggLS0tLQojIGxpYnJhcnkoZmFjdG9leHRyYSkKZnZpel9jbHVzdGVyKAogIG9iamVjdCA9IGh5YnJpZENpcnJob3NpcywKICBzdGFuZCA9IEZBTFNFLAogIGdlb20gPSAicG9pbnQiLAogIG1haW4gPSAiSHlicmlkIENsdXN0ZXIgUGxvdCAtIEluaXRpYWwgbW9kZWwiCikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBTdGFnZXNQYWxldHRlKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gU3RhZ2VzUGFsZXR0ZSkgKwogIHRoZW1lX2J3KCkgCgpgYGAKCldlIGNhbiBhbHNvIHBsb3Qgb3VyIHJlZmluZWQgbW9kZWwsIGFzIHlvdSBjYW4gc2VlIHRoZSBwbG90IGJlbG93IGlkZW50aWZpZXMgNSBjbGVhciBjbHVzdGVycy4KCmBgYHtyfQoKIyBQbG90IHRoZSBmaW5hbCBjbHVzdGVyaW5nIGZvciBoeWJyaWQgYXBwcm9hY2ggLS0tLQojIGxpYnJhcnkoZmFjdG9leHRyYSkKZnZpel9jbHVzdGVyKAogIG9iamVjdCA9IGh5YnJpZENpcnJob3NpczIsCiAgc3RhbmQgPSBGQUxTRSwKICBnZW9tID0gInBvaW50IiwKICBtYWluID0gIkh5YnJpZCBDbHVzdGVyIFBsb3QgLSBSZWZpbmVkIG1vZGVsIgopICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gU3RhZ2VzUGFsZXR0ZSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IFN0YWdlc1BhbGV0dGUpICsKICB0aGVtZV9idygpIAoKYGBgCgpOb3cgd2UgY2FuIGdvIGJhY2sgdG8gb3VyIGluaXRpYWwgZ29hbDogY29tcGFyaW5nIGhvdyB0aGUgY3VzdGVycyBjb21wYXJlIHRvIHRoZSBTdGFnZSB2YXJpYWJsZQoKYGBge3J9CmNpcnJob3Npc0NsdXN0ZXIyJGNsdXN0ZXIgPC0gaHlicmlkQ2lycmhvc2lzJGNsdXN0ZXIKIyBDYWxjdWxhdGUgbWVhbiAob3IgbWVkaWFuLCBldGMuKSBmb3IgZWFjaCB2YXJpYWJsZSBpbiBlYWNoIGNsdXN0ZXIKbGlicmFyeShkcGx5cikKY2x1c3Rlcl9zdW1tYXJ5IDwtIGNpcnJob3Npc0NsdXN0ZXIyICU+JQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIHN1bW1hcmlzZV9hbGwoZnVucyhtZWFuKC4sIG5hLnJtID0gVFJVRSkpKSAjIFJlcGxhY2UgbWVhbiB3aXRoIG1lZGlhbiBvciBhbnkgb3RoZXIgZnVuY3Rpb24gYXMgbmVjZXNzYXJ5CmthYmxlKGNsdXN0ZXJfc3VtbWFyeSAlPiUgZHBseXI6OnNlbGVjdCgtU3RhdHVzLCAtRHJ1ZywgLSBTZXgsIC1FZGVtYSwgLVN0YWdlKSwgbGFiZWwgPSAiQ2x1c3RlciBTdW1tYXJ5IikKZ2dwbG90KGNpcnJob3Npc0NsdXN0ZXIyLCBhZXMoeCA9IGZhY3RvcihjbHVzdGVyKSwgZmlsbCA9IGZhY3RvcihTdGFnZSkpKSArCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgU3RhZ2VzIHdpdGhpbiBDbHVzdGVycyIsCiAgICAgICB4ID0gIkNsdXN0ZXIiLAogICAgICAgeSA9ICJDb3VudCIsCiAgICAgICBmaWxsID0gIlN0YWdlIikgKwogIHRoZW1lX21pbmltYWwoKQoKYGBgCgpXZSBjYW4gc2VlIHRoYXQgdGhlIGNsdXN0ZXJzIGFyZSBub3QgZXZlbmx5IGRpc3RyaWJ1dGVkLiBUaGlzIGlzIGEgZ29vZCBzaWduLCBhcyBpdCBtZWFucyB0aGF0IHRoZSBjbHVzdGVycyBhcmUgbm90IGp1c3QgYSByYW5kb20gZ3JvdXBpbmcgb2YgdGhlIGRhdGEuCgojIERpc2N1c3Npb24KCiMgQXV0aG9yIENvbnRyaWJ1dGlvbnMKCiMgUmVmZXJlbmNlcy0tQ2l0YXRpb24gc3R5bGUgaXMgeW91ciBjaG9pY2UsIGJ1dCBhbGwgc291cmNlcyBzaG91bGQgYmUgZG9jdW1lbnRlZCAoYm90aCBpbiB0ZXh0IGFuZCBpbiB0aGUgUmVmZXJlbmNlcyBzZWN0aW9uKS4gVGhpcyBpbmNsdWRlcyB3aGVyZSB5b3UgZ290IHlvdXIgZGF0YS4KCiMgQ29kZSBBcHBlbmRpeAo=